使用Jest测试Nest.js应用时异步未等待导致测试报错
我用Jest测试Nest.js微服务应用时,在使用lastValueFrom、Promise.all和timeout组合时遇到测试无法正常结束的问题,报错信息如下:
Cannot log after tests are done. Did you forget to wait for something async in your test?
场景说明:我有一个执行异步操作的formattedProduct函数,在getProductsList方法中通过Promise.all等待所有该函数的Promise完成。测试代码通过NATS发送请求并等待响应,但测试始终无法正常收尾。
代码片段
1. formattedProduct函数
async formattedProduct( product_id: string, product: any, output_template: CardProductTemplate | PageProductTemplate, curCityId: any = false, customerId: any = false, ){ // 其他业务逻辑代码... console.log('product calculated'); return product; }
2. getProductsList服务方法
public async getProductsList(params: GetProductsListDto) { // 其他前置方法逻辑... const promisesFormattedProduct: Promise<any>[] = []; products.forEach((product) => { promisesFormattedProduct.push( this.formattedProduct( product.id, product, new CardProductTemplate(), params.city_id, params.customer_id, ), ); }); const formatted_products = await Promise.all(promisesFormattedProduct); const result = { formatted_products, }; return result; }
3. 控制器代码
@MessagePattern([ 'api-gateway.common.getProductsList', 'api-gateway.common.getProductsListByBrand', 'api-gateway.common.getUsedProductsList', 'api-gateway.common.getSimilarProductsList', ]) async getProductsList(params: GetProductsListDto) { const products= await this.productService.getProductsList(params); return products; }
4. 测试文件
import { Test, TestingModule } from '@nestjs/testing'; import { INestMicroservice } from '@nestjs/common'; import { Transport, ClientProxyFactory, ClientProxy } from '@nestjs/microservices'; import { AppModule } from '@src/app.module'; import { lastValueFrom } from 'rxjs/internal/lastValueFrom'; import { timeout } from 'rxjs'; describe('ProductController (NATS)', () => { let app: INestMicroservice; let client: ClientProxy; beforeAll(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleRef.createNestMicroservice({ transport: Transport.NATS, options: { servers: ['nats://nats:4222'], }, }); await app.listen(); client = ClientProxyFactory.create({ transport: Transport.NATS, options: { servers: ['nats://nats:4222'], }, }); await client.connect(); }); afterAll(async () => { // if (client) { // await client.close(); // } // if (app) { // await app.close(); // } }); it('should return response for unauthorized', async () => { const params = { rate_id: 643, category_id: '0b5d5269-5e79-11e3-bd86-002590552cc0', page: 0, onpage: 20, sort_by: 'relevance', sort_order: 'asc', filter: {}, city_id: '90', customer_id: null, selection_type: 'from_category' }; const response = await lastValueFrom(client.send('api-gateway.common.getProductsList', params).pipe(timeout(60000))); console.log('response get'); expect(response).toBeDefined(); return; }, 60000); });
问题原因及解决方法
核心原因
测试完成后仍有未收尾的异步操作在后台运行(比如未关闭的连接、未被正确等待的Promise),导致console.log在测试结束后触发,触发Jest的报错逻辑。
解决步骤
恢复
afterAll中的连接关闭逻辑
取消afterAll里的注释,确保测试结束后关闭NATS客户端和微服务应用,彻底终止所有异步资源:afterAll(async () => { await client.close(); await app.close(); });检查
formattedProduct内部的异步逻辑
确保formattedProduct函数内的所有异步操作(比如数据库查询、外部API调用)都被正确await,没有遗漏的后台任务。如果存在未被等待的异步代码,会导致Promise.all提前完成,但实际还有任务在运行。验证
Promise.all的执行完整性
可以在Promise.all后添加日志,确认所有格式化任务确实已经完成:const formatted_products = await Promise.all(promisesFormattedProduct); console.log('All products formatted'); // 确认所有异步任务完成测试环境禁用不必要的日志
可以在测试中临时禁用console.log,避免测试结束后日志干扰Jest的判断:beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}); }); afterEach(() => { jest.restoreAllMocks(); });
内容的提问来源于stack exchange,提问作者Sergei Illarionov

