TransWikia.com

Spying module and mocking module function

Stack Overflow Asked by Ninita on January 16, 2021

I’m writing some tests for my component but I’m facing some troubles here…

Here is my Game component:

import React from 'react';
import User from './User';
import Board from './Board';
import Deck from './Deck';

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.board = new Board();
    this.deck = new Deck();

    //some more code
  }

  componentDidMount() {
    this.initializeUser("xpto");
    //some more code
  }

  //some more code

  initializeUser(name) {
    const user = new User(name, this.deck);
    //some more code

    user.pickCards();
    //some more code
  }

  //some more code

  render() {
    return (
      <div className="game-container">
          something to show
          <div id="deck"></div>
      </div>
    );
  }
}


Game.propTypes = {
};

Game.defaultProps = {
};

export default Game;

My Board class:

export default class Board {
  //some code
}

My Deck class:

export default class Deck {
  constructor(props) {
    //some more code

    this.cardsLeft = 52;
    this.lastPick = 0;

    //some more code
  }

  pickCards() {
    this.lastPick = 4;
    this.cardsLeft -= this.lastPick;
    const deckElem = document.getElementById("deck");
    deckElem.innerHTML = this.cardsLeft;
    return this.lastPick;
  }

  //some more code
}

My User class:

class User {
  constructor(name, deck) {
    this.name = name;
    this.tableDeck = deck;
    this.cards = 0;
    //some more code
  }

  //some more code

  pickCards() {
    const newCards = this.tableDeck.pickCards();
    this.cards += newCards;
    //some code
  }

  //some more code
}

export default User;

Now, at my tests I’m trying to test if the Board and User are called and if the pickCards() is called too.

Here are my tests:

import React from 'react';
import { mount } from 'enzyme';
import Game from './Game';
import User from './User';
import Board from './Board';

describe('Game start', () => {
  let container;

  beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
  });

  afterEach(() => {
    document.body.removeChild(container);
    container = null;
  });

  it("test where I'm having problems", () => {
    const boardSpy = jest.spyOn(Board, 'constructor'),
      userSpy = jest.spyOn(User, 'constructor'),
      pickCardMock = jest.fn();

    User.pickCard = pickCardMock;

    const wrapper = mount(<Game />, { attachTo: container });

    expect(boardSpy).toHaveBeenCalledTimes(1);
    expect(userSpy).toHaveBeenCalledTimes(1);
    expect(pickCardMock).toHaveBeenCalledTimes(1);

    //some more code
  });

  it("example test where I need to test everything without mocks", () => {
    const wrapper = mount(<Game />, { attachTo: container });

    expect(wrapper.find("#deck").text()).toEqual('48');

    //some code
  });

  //some more tests
});

I don’t want to mock Board and User because I need everything work normally on it. But I want to spy them to check if they were really called. And I want to mock pickCard() from User.

I already tried to use jest.mock('./board'); and require('board') (for example) only inside my it() test but it didn’t work. And now I’m trying to spy the components constructors.

But the expect(boardSpy).toHaveBeenCalledTimes(1) fails saying that was called 0 times and not 1 time.

And the pickCardMock seems not being linked to the User module because when debugging pickCard is a normal function and not a mock function and expect(pickCardMock).toHaveBeenCalledTimes(1) receives 0 too instead of 1.

Anyone knows how to solve these two problems (spy a module and mock a function in another module)?

Again:

I don’t want to mock things for the whole test suite, I just want to
mock for a single test (and I want to be able to spy a module call).

You can find all this code here.

3 Answers

In case you haven't tried this yet:

You can spy on User.pickCard with jest.spyOn(User.prototype, 'pickCard').

Another option for spying on Board and User while keeping the original implementation in place is with jest.requireActual:

import Board from './Board';

jest.mock('./Board', () => {
  const BoardSpy = jest.requireActual('./Board').default;
  return {
    default: jest.fn((...args) => new BoardSpy(...args)),
    __esModule: true,
  };
});

it('instantiates a Board', () => {
  const board = new Board();
  expect(Board).toBeCalled();
});

Correct answer by helloitsjoe on January 16, 2021

It looks like you are trying to do both unit testing and integration testing within the same test file - best practice recommends that you write separate files for them. That alone would solve your problem.

Also, in your initializeUser method, you are creating a User, calling pickCards, and then... It doesn't look like you are storing the variable anywhere. Do you not need access to that user variable again?

EDIT: removed mistake in answer.

Answered by Jake Stokes on January 16, 2021

You can use jest.mock(moduleName, factory, options) to mock User and Board components.

E.g.

Game.jsx:

import React, { Component } from 'react';
import User from './User';
import Board from './Board';

class Game extends Component {
  constructor(props) {
    super(props);
    this.board = new Board({});
  }

  initializeUser(name) {
    const user = new User(name);
    user.pickCards();
  }

  render() {
    return <div className="game-container"></div>;
  }
}

export default Game;

Board.jsx:

import React, { Component } from 'react';

class Board extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    return 'Board';
  }
}

export default Board;

User.jsx:

import React, { Component } from 'react';

class User extends Component {
  constructor(props) {
    super(props);
    this.name = this.props.name;
  }

  pickCards() {
    console.log('pick cards real implementation');
  }

  render() {
    return 'User';
  }
}

export default User;

Game.test.jsx:

import React from 'react';
import { mount } from 'enzyme';
import Game from './Game';
import BoardMock from './Board';
import UserMock from './User';

jest.mock('./User', () => {
  const mUser = { pickCards: jest.fn() };
  return jest.fn(() => mUser);
});

jest.mock('./Board', () => jest.fn());

describe('62199135', () => {
  afterAll(() => {
    jest.resetAllMocks();
  });
  it('should pass', () => {
    const userMock = new UserMock();
    const wrapper = mount(<Game />);
    const gameInstance = wrapper.instance();
    gameInstance.initializeUser('some name');

    expect(BoardMock).toHaveBeenCalledTimes(1);
    expect(UserMock).toHaveBeenCalledWith('some name');
    expect(userMock.pickCards).toHaveBeenCalledTimes(1);
  });
});

You can reset all mocks after running all test cases.

Unit test result:

 PASS  stackoverflow/62199135/Game.test.jsx (10.16s)
  62199135
    ✓ should pass (33ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 Game.jsx |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        11.474s, estimated 12s

Answered by slideshowp2 on January 16, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP