Jest SpyOn异常:实际方法被调用,未执行模拟实现
问题分析
你的测试失效的核心原因是:GameService内部直接硬编码实例化了GameFactory(const gameFactory = new GameFactory()),Jest对导出类的mock无法影响到函数内部新建的实例,所以实际的createNewGameWithInitialPlayer方法依然会被调用。
解决方案
解决思路是通过依赖注入将GameFactory传入GameService,而不是在内部直接创建,这样测试时可以替换为mock实例。
步骤1:重构GameService,支持依赖注入
修改GameService.ts,把GameFactory作为可选参数传入,默认值保持原有逻辑,不影响现有业务代码:
// GameService.ts const GameService = (games: {[index: string]: Game}, gameFactory = new GameFactory()) => { const playerFactory = PlayerFactory() const createRoom = ({name, device, socketID}: {name: string, device: string, socketID: string}): RoomResponse => { const player = playerFactory.createNewPlayer(name, device, socketID) if (player && player.id) { const game: Game|undefined = gameFactory.createNewGameWithInitialPlayer(player) // ... 原有业务逻辑 } // ... 原有业务逻辑 }
步骤2:修改测试代码,传入Mock实例
在测试中创建一个mock的GameFactory实例,指定createNewGameWithInitialPlayer返回undefined,然后传给GameService:
// GameService.test.ts import { GameFactory } from '../Factories/GameFactory' import GameService from '../GameService' describe('Testing Game Service', () => { test('createRoom handles createNewGameWithInitialPlayer returning undefined', () => { // 构建mock的GameFactory实例 const mockGameFactory = { createNewGameWithInitialPlayer: jest.fn().mockReturnValue(undefined) } as unknown as GameFactory // 传入mock实例初始化GameService const gameService = GameService({}, mockGameFactory) const response: RoomResponse = gameService.createRoom({ name: 'Player 1', device: DevicesEnum.ios, socketID: 'some-socket-id' }) // 验证mock方法是否被正确调用 expect(mockGameFactory.createNewGameWithInitialPlayer).toHaveBeenCalled() // 这里添加针对response的断言,比如检查返回值是否符合预期 }) })
补充方案(无需重构的临时写法)
如果暂时不想重构代码,也可以用Jest的mock方法直接替换整个GameFactory模块:
// GameService.test.ts import * as GameFactoryModule from '../Factories/GameFactory' import GameService from '../GameService' // 提前mock整个模块 jest.mock('../Factories/GameFactory', () => { return { GameFactory: jest.fn().mockImplementation(() => { return { createNewGameWithInitialPlayer: jest.fn().mockReturnValue(undefined) } }) } }) describe('Testing Game Service', () => { test('createRoom', () => { const gameService = GameService({}) const response: RoomResponse = gameService.createRoom({ name: 'Player 1', device: DevicesEnum.ios, socketID: 'some-socket-id' }) expect(GameFactoryModule.GameFactory).toHaveBeenCalled() expect(GameFactoryModule.GameFactory.prototype.createNewGameWithInitialPlayer).toHaveBeenCalled() }) })
不过这种方式耦合性较高,推荐优先使用依赖注入的方案,代码更易维护和测试。
内容的提问来源于stack exchange,提问作者lost_programmer
相关产品推荐
相关产品推荐

