You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jest误判测试失败?NestJS模拟Repository异常问题排查

NestJS Jest测试:捕获异步异常后测试仍失败的问题解决

问题场景

我有一个NestJS TypeScript应用,其中MyService依赖MyRepository。我希望测试当myRepository.upsert(...)抛出异常时,myService.doSomething()能按预期处理错误(捕获异常并设置响应的errorCode)。

我通过jest.spyOn结合mockImplementation模拟异常:

jest.spyOn(myRepository, 'upsert').mockImplementation((myEntity: MyEntity) =>
    Promise.reject(new Error('SAVE FAILED'))
)

注意doSomething()既没有重新抛出该异常,也没有抛出其他异常,测试中的断言已验证错误被正确捕获处理,但Jest仍将doSomething_failedUpdate测试标记为失败。

完整代码

export class MyResponse {
  // ...
  errorCode: number
}

// 数据库CRUD仓库
@Injectable()
class MyRepository {
  // ...
  async upsert(myEntity: MyEntity): Promise<MyEntity> {
    // ...
  }
}

// 依赖仓库的服务
@Injectable()
export class MyService {      
  constructor(
    private myRepository: MyRepository
  )
  
  async doSomething(): Promise<MyResponse> {  
    let response: MyResponse = new MyResponse()
      
    try {
      // 此处省略myEntity定义逻辑
      await this.myRepository.upsert(myEntity)      
    } catch (error) {
      response.errorCode = 100
    }

    return response
  }
}

// Jest测试代码
import { expect } from 'chai'
import { INestApplication } from '@nestjs/common'
import { Test, TestingModule } from '@nestjs/testing'
// 其他导入

describe('Test MyService.doSomething', () => {
  let appModuleFixture: TestingModule = null
  let app: INestApplication = null
  let myService: MyService = null
  let myRepository: MyRepository = null
  
  beforeEach(async () => {
    appModuleFixture = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()
  
    app = appModuleFixture.createNestApplication()
    app.useGlobalFilters(new DefaultExceptionFilter(), new MyExceptionFilter())
    await app.init()
  
    myRepository = appModuleFixture.get(MyRepository)
    myService = appModuleFixture.get(MyService)
  })
  
  afterEach(async () => {
    await app.close()
    jest.resetAllMocks()
  })
  
  // 其他测试用例:'doSomething_ok'...

  it('doSomething_failedUpdate', async () => {
    // GIVEN:
    jest.spyOn(myRepository, 'upsert').mockImplementation((myEntity: MyEntity) =>
        Promise.reject(new Error('SAVE FAILED'))
      )
    
    // WHEN:
    let response = await myService.doSomething()
    
    // THEN:
    expect(response.errorCode).to.be.equal(100)
  })
})

Jest错误输出

FAIL test/services/my.service.e2e-spec.ts (13.048 s)
  Test MyService.doSomething
    ✕ doSomething_failedUpdate (9490 ms)
    ○ skipped doSomething_ok

  ● Test MyService.doSomething › doSomething_failedUpdate

    SAVE FAILED

      448 |       .spyOn(myRepository, 'upsert')
      449 |       .mockImplementation((myEntity: MyEntity) =>
    > 450 |         Promise.reject(new Error('SAVE FAILED'))
          |                        ^
      451 |       )
      452 |
      453 |     // WHEN

      at MyRepository.<anonymous> (services/my.service.e2e-spec.ts:450:24)
      ...

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 skipped, 2 total

问题原因

问题源于Jest的未处理Promise拒绝检测机制:

  • 当使用mockImplementation手动返回Promise.reject时,Jest会将这个Promise的拒绝视为未被处理的异常,即使你的业务代码已经通过try/catch捕获了该异常。
  • mockImplementation没有针对异步函数的异常场景做特殊处理,导致Jest的mock系统无法正确追踪这个Promise的处理状态,最终误判为测试失败。

解决方案

有两种可靠的解决方式:

1. 使用Jest原生的mockRejectedValue方法

这是Jest专门为模拟异步函数抛出异常设计的API,能正确处理Promise的拒绝状态,避免触发Jest的未处理Promise拒绝检测:

it('doSomething_failedUpdate', async () => {
  // GIVEN:
  jest.spyOn(myRepository, 'upsert').mockRejectedValue(new Error('SAVE FAILED'))
  
  // WHEN:
  const response = await myService.doSomething()
  
  // THEN:
  expect(response.errorCode).to.be.equal(100)
})

2. 增加mock函数调用的断言(可选,增强测试严谨性)

通过断言确认mock函数被调用,能让Jest明确这个Promise的拒绝已经被业务代码处理,进一步避免误判:

it('doSomething_failedUpdate', async () => {
  // GIVEN:
  const upsertSpy = jest.spyOn(myRepository, 'upsert').mockRejectedValue(new Error('SAVE FAILED'))
  
  // WHEN:
  const response = await myService.doSomething()
  
  // THEN:
  expect(response.errorCode).to.be.equal(100)
  expect(upsertSpy).toHaveBeenCalled() // 确认mock函数被正确调用
})

另外,确保doSomething函数中的myEntity变量已正确定义(否则会先抛出同步ReferenceError,干扰测试逻辑)。

内容的提问来源于stack exchange,提问作者burebista

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 12:05:31