lost_programmer
lost_programmer

Reputation: 91

Jest SpyOn - Actual Method is Called, Instead of the Mocked Implementation

I've searched high and low to try and get my Jest test to work but have gotten nowhere in my search.

I've got a function (createRoom) that I want to unit test and I want to mock a method (gameFactory.createNewGameWithInitialPlayer()) call.

GameService.ts

const GameService = (games: {[index: string]: Game}) => {
  const playerFactory = PlayerFactory()
  const gameFactory = new GameFactory()

  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)
        ...
    }
    ...
}

GameFactory.ts

export class GameFactory {
    createNewGameWithInitialPlayer = (player: Player): Game|undefined => {
        const game = new Game()
        game.spectators[player.id as any as number] = player

        return game
    }
}

GameService.test.ts

import * as gameFactory from '../Factories/GameFactory'

describe('Testing Game Service', () => {
    test('createRoom', () => {
        jest.spyOn(gameFactory, 'GameFactory').mockReturnValue({ createNewGameWithInitialPlayer: jest.fn().mockReturnValue(undefined)})

        const response: RoomResponse = gameService.createRoom({
            name: 'Player 1',
            device: DevicesEnum.ios,
            socketID: 'some-socket-id'
        })
    ...
    }
    ...
}

In GameService.test.ts I am mocking the return value of the createNewGameWithInitialPlayer method call. However, when I run the test, the actual implementation of it runs, instead of my mocked version. For this test in particular, I want the createNewGameWithInitialPlayer method to return undefined, but that does not happen, it appears to be calling the actual method implementation.

Upvotes: 1

Views: 1753

Answers (2)

Diego
Diego

Reputation: 124

If you want to override the createNewGameWithInitialPlayer and return what you want then, you have to mock the import of GameFactory class in your test.

// Here you are creating your mock and saying the default return Game object
const mockCreateNewGameWithInitialPlayer = jest.fn().mockImplementation(() => new Game());

// Here you say to jest that any file who wants to import "GameFactory"
// will import this fake class
jest.mock('rootofGameFactory/GameFactory', () => ({
    GameFactory: function(){
      return {
        // and when any file wants to execute this method, will execute my mock
        createNewGameWithInitialPlayer: mockCreateNewGameWithInitialPlayer
      }
    }
}))

describe('Testing Game Service', () => {
    test('createRoom', () => {
        const response: RoomResponse = gameService.createRoom({
            name: 'Player 1',
            device: DevicesEnum.ios,
            socketID: 'some-socket-id'
        })
    ...
    }
    ...
}

If you want to change the return object of your mocked method, you have to do it like this...

test('createRoom 2', () => {
        //Here you say to jest, just ONCE (for this test) return an instance of Game2
        mockCreateNewGameWithInitialPlayer.mockImplementationOnce(() => new Game2())
        const response: RoomResponse = gameService.createRoom({
            name: 'Player 1',
            device: DevicesEnum.ios,
            socketID: 'some-socket-id'
        })
    ...
    }

Upvotes: 1

Julien Zakaib
Julien Zakaib

Reputation: 194

The jest documentation mentions

By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries. If you want to overwrite the original function, you can use jest.spyOn(object, methodName).mockImplementation(() => customImplementation) or jest.replaceProperty(object, methodName, jest.fn(() => customImplementation));

So you could do something like

jest.spyOn(gameFactory, 'GameFactory').mockImplementation(() => { return undefined })

Upvotes: 0

Related Questions