Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spying module and mocking module function

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.

like image 436
Ninita Avatar asked Nov 06 '22 07:11

Ninita


1 Answers

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
like image 142
slideshowp2 Avatar answered Nov 15 '22 12:11

slideshowp2