使用Vitest和依赖注入测试CastVoteCommandHandler时Mock报错求助
解决Vitest中
mockResolvedValueOnce调用失败的问题 问题场景
使用Vitest测试依赖注入(DI)的CastVoteCommandHandler类,该类依赖VoteRepository。尝试mockVoteRepository时测试失败,报错如下:
TypeError: findVoteMock.mockResolvedValueOnce is not a function
原业务代码
@injectable() export class CastVoteCommandHandler { public constructor( @inject(symbols.voteRepository) private readonly voteRepository: VoteRepository ) {} public async execute(payload: CastVoteData) { if (await this.voteRepository.hasVotedTwiceOnAnswer(payload)) { throw new VoteProcessingError('You can only vote once on the same answer.') } if (!(await this.voteRepository.checkIfAnswerBelongsToPool(payload))) { throw new VoteProcessingError('Answer does not exist in the current pool.') } const existingVote = await this.voteRepository.findVoteInPoolByVoter(payload) if (existingVote) { const { vote } = existingVote await this.voteRepository.updateVote({ voteId: vote.getId(), answerId: payload.answerId, }) } else { await this.voteRepository.createVote(payload) } SocketIOService.emitVote(payload.poolId) } }
原测试代码
import { faker } from '@faker-js/faker' import { ContainerSingleton } from 'container' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { type CastVoteData } from 'modules/vote/api/schemas' import { type VoteRepository } from 'modules/vote/infrastructure/repositories' import { Vote } from 'modules/vote/domain/entities' import { symbols } from 'modules/vote/symbols' import { type CastVoteCommandHandler } from './castVoteCommandHandler' describe('CastVoteCommandHandler', () => { let castVoteCommandHandler: CastVoteCommandHandler let voteRepository: VoteRepository let castVoteData: CastVoteData beforeEach(() => { const container = ContainerSingleton.getInstance() voteRepository = container.get(symbols.voteRepository) castVoteCommandHandler = container.get(symbols.castVoteCommandHandler) castVoteData = { poolId: faker.datatype.uuid(), voterId: faker.datatype.uuid(), answerId: faker.datatype.uuid(), } }) afterEach(() => { vi.restoreAllMocks() }) it('should successfully cast vote', async () => { // Arrange const createVoteMock = vi.mocked(voteRepository.createVote) const findVoteMock = vi.mocked(voteRepository.findVoteInPoolByVoter) const updateVoteMock = vi.mocked(voteRepository.updateVote) const hasVotedTwiceOnAnswerMock = vi.mocked(voteRepository.hasVotedTwiceOnAnswer) const checkIfAnswerBelongsToPoolMock = vi.mocked(voteRepository.checkIfAnswerBelongsToPool) // Mock vote data const mockVoteData = { id: faker.datatype.uuid(), answerId: faker.datatype.uuid(), voterId: faker.datatype.uuid(), } // Create mock Vote instance const mockVote = new Vote(mockVoteData) // Set up your mocks findVoteMock.mockResolvedValueOnce({ vote: mockVote }) hasVotedTwiceOnAnswerMock.mockResolvedValueOnce(false) checkIfAnswerBelongsToPoolMock.mockResolvedValueOnce(true) // Act await castVoteCommandHandler.execute(castVoteData) // Assert expect(createVoteMock).toHaveBeenCalledWith(castVoteData) expect(updateVoteMock).not.toHaveBeenCalled() }) })
问题原因
从容器中获取的voteRepository是真实实现类的实例,而非Vitest的mock对象。vi.mocked()仅做TypeScript类型断言,不会将真实方法转换为可mock的函数,因此调用mockResolvedValueOnce时会报错。
解决方案
需要在测试前替换容器中的真实VoteRepository为Vitest mock实例,具体步骤如下:
1. 创建Mock的VoteRepository实例
在beforeEach中,用vi.fn()创建每个方法的mock,然后将这个mock实例注入容器,覆盖原有的真实实例。
2. 修改测试用例中的mock设置
直接使用mock实例的方法来设置返回值,无需再用vi.mocked()做类型断言。
修改后的测试代码
import { faker } from '@faker-js/faker' import { ContainerSingleton } from 'container' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { type CastVoteData } from 'modules/vote/api/schemas' import { type VoteRepository } from 'modules/vote/infrastructure/repositories' import { Vote } from 'modules/vote/domain/entities' import { symbols } from 'modules/vote/symbols' import { type CastVoteCommandHandler } from './castVoteCommandHandler' describe('CastVoteCommandHandler', () => { let castVoteCommandHandler: CastVoteCommandHandler // 声明为Mock类型,获得mock方法提示 let voteRepository: jest.Mocked<VoteRepository> let castVoteData: CastVoteData beforeEach(() => { const container = ContainerSingleton.getInstance() // 创建Mock的VoteRepository实例,每个方法都用vi.fn() mock voteRepository = { hasVotedTwiceOnAnswer: vi.fn(), checkIfAnswerBelongsToPool: vi.fn(), findVoteInPoolByVoter: vi.fn(), updateVote: vi.fn(), createVote: vi.fn() } as jest.Mocked<VoteRepository> // 替换容器中的真实实例为mock实例 container.bind(symbols.voteRepository).toConstantValue(voteRepository) // 从容器获取handler(此时它依赖的是mock的voteRepository) castVoteCommandHandler = container.get(symbols.castVoteCommandHandler) castVoteData = { poolId: faker.datatype.uuid(), voterId: faker.datatype.uuid(), answerId: faker.datatype.uuid(), } }) afterEach(() => { vi.clearAllMocks() }) it('should successfully cast vote when no existing vote', async () => { // Arrange const mockVoteData = { id: faker.datatype.uuid(), answerId: faker.datatype.uuid(), voterId: faker.datatype.uuid(), } const mockVote = new Vote(mockVoteData) // 直接设置mock方法的返回值 voteRepository.findVoteInPoolByVoter.mockResolvedValueOnce(null) voteRepository.hasVotedTwiceOnAnswer.mockResolvedValueOnce(false) voteRepository.checkIfAnswerBelongsToPool.mockResolvedValueOnce(true) // Act await castVoteCommandHandler.execute(castVoteData) // Assert expect(voteRepository.createVote).toHaveBeenCalledWith(castVoteData) expect(voteRepository.updateVote).not.toHaveBeenCalled() }) it('should update vote when existing vote exists', async () => { // Arrange const mockVoteData = { id: faker.datatype.uuid(), answerId: faker.datatype.uuid(), voterId: faker.datatype.uuid(), } const mockVote = new Vote(mockVoteData) voteRepository.findVoteInPoolByVoter.mockResolvedValueOnce({ vote: mockVote }) voteRepository.hasVotedTwiceOnAnswer.mockResolvedValueOnce(false) voteRepository.checkIfAnswerBelongsToPool.mockResolvedValueOnce(true) // Act await castVoteCommandHandler.execute(castVoteData) // Assert expect(voteRepository.updateVote).toHaveBeenCalledWith({ voteId: mockVote.getId(), answerId: castVoteData.answerId, }) expect(voteRepository.createVote).not.toHaveBeenCalled() }) })
额外说明
- 使用
jest.Mocked<VoteRepository>可以让TypeScript识别mock对象的方法,获得mockResolvedValueOnce等方法的类型提示。 - 测试完成后用
vi.clearAllMocks()清空mock的调用记录,避免测试间的污染。
内容的提问来源于stack exchange,提问作者IvonaK
相关产品推荐
相关产品推荐

