如何测试写入流时的错误?Node.js流测试异常排查
_write方法抛出的异常无法被捕获 我正在测试应用中不同模块抛出的错误。该应用会把多文件上传请求的流通过加密管道传输到S3存储桶。
我能在加密流的_read方法里模拟异常并被正确捕获,但在目标可写流的_write方法里用同样方式抛出异常时,会出现未处理异常的情况。
下面是能正常运行的测试代码(模拟加密流抛出错误):
it('error in encryption stream', async () => { const encryptionStream = new DuplexMock(); encryptionStream._read = () => { throw new Error('Test Exception') }; const writeStream = new BufferWritableMock(); CryptoService.encryptionStream.mockImplementation(()=> encryptionStream); S3Service.uploadFromStream.mockImplementation(() => writeStream); const result = await supertest(appInstance) .post(`${storePath}?skip-encryption=false`) .attach('file', filePath); expect(result.statusCode).toBe(500); });
是不是我的处理方式有问题?
更新1
之前为排查问题把encryptionStream改成了DuplexMock,但实际应该用PassThrough;另外原代码里writeStream没被调用,现在已经更新了上面的代码。
更新2
下面是无法正常运行的测试代码(模拟S3可写流抛出错误):
it('error in S3 stream', async () => { const encryptionStream = new PassThrough(); const writeStream = new ObjectWritableMock(); writeStream._write = (chunk, enc, next) => { new Error('Test Exception'); }; CryptoService.encryptionStream.mockImplementation(()=> encryptionStream); S3Service.uploadFromStream.mockImplementation(() => writeStream); const result = await supertest(appInstance) .post(`${storePath}?skip-encryption=false`) .attach('file', filePath) expect(result.statusCode).toBe(500); }, 10*1000 );
问题出在可写流_write方法的异常处理逻辑上,Node.js流的_write有特定的错误处理规则:
错误传递方式错误
在_write里直接throw Error或者只创建Error实例不处理都无效,因为_write是异步方法,抛出的异常会进入Node.js全局异常处理,不会被流的错误事件捕获。正确做法是通过回调函数next传递错误:writeStream._write = (chunk, enc, next) => { // 调用next并传入错误对象,触发流的error事件 next(new Error('Test Exception')); };确保业务代码监听流的error事件
你的应用代码必须给S3可写流添加error事件监听,否则即使通过next传递错误,也会变成未处理异常。比如业务代码里应该有类似逻辑:const s3Stream = S3Service.uploadFromStream(); s3Stream.on('error', (err) => { // 终止请求并返回500状态码 res.status(500).send('上传失败'); });修正测试代码的错误逻辑
你当前的测试代码里,new Error('Test Exception')只是创建了错误对象但未传递,完全不会触发错误流程。必须通过next回调把错误传递出去,这样流才会触发error事件,被应用的错误处理逻辑捕获,最终返回500状态码。
调整后的测试代码如下:
it('error in S3 stream', async () => { const encryptionStream = new PassThrough(); const writeStream = new ObjectWritableMock(); writeStream._write = (chunk, enc, next) => { // 通过next传递错误 next(new Error('Test Exception')); }; CryptoService.encryptionStream.mockImplementation(()=> encryptionStream); S3Service.uploadFromStream.mockImplementation(() => writeStream); const result = await supertest(appInstance) .post(`${storePath}?skip-encryption=false`) .attach('file', filePath) expect(result.statusCode).toBe(500); }, 10*1000);
内容的提问来源于stack exchange,提问作者Dog Ears

