Jest: a mocking console.

Problem:

I have a simple React component that I am using to learn how to test components using Jest and Enzyme. When I work with props, I added a module prop-types

to validate properties in development. prop-types

is used console.error

to alert when required requisites are not passed or when requisites are of the wrong data type.

I wanted to make fun of console.error

it to count the number of times it was called prop-types

when I passed in missing / incorrectly typed props.

Using this simplified example component and test, I would expect two tests to behave as such:

  • The first test with zero props 0/2 should catch the mock call twice.
  • A second test with 1/2 of the requisites required should catch a fake call that gets called once.

Instead, I get this:

  • The first test succeeds.
  • The second test fails, complaining that the mock function was called zero time.
  • If I change the order of the tests, the former works and the latter fails.
  • If I split each test into a separate file, both work.
  • console.error

    the inference is suppressed, so it clears it of laughter for both.

I'm sure I'm missing something obvious like clearing a false error or something.

When I use the same structure against a module that exports a function by calling console.error

some arbitrary number of times, everything works.

It's when I test the enzyme / react that I hit this wall after the first test.

App.js example:

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class App extends Component {

  render(){
    return(
      <div>Hello world.</div>
    );
  }
};

App.propTypes = {
  id : PropTypes.string.isRequired,
  data : PropTypes.object.isRequired
};

      

App.test.js example

import React from 'react';
import { mount } from 'enzyme';
import App from './App';

console.error = jest.fn();

beforeEach(() => {
  console.error.mockClear();
});

it('component logs two errors when no props are passed', () => {
  const wrapper = mount(<App />);
  expect(console.error).toHaveBeenCalledTimes(2);
});

it('component logs one error when only id is passed', () => {
  const wrapper = mount(<App id="stringofstuff"/>);
  expect(console.error).toHaveBeenCalledTimes(1);
});

      

Final note: Yes, it is better to write a component to generate some user friendly output if props are missing, then check for that. But once I found this behavior, I wanted to figure out what I was doing wrong as a way to improve my understanding. It is clear that I missed something.

+5


source to share


4 answers


You haven't missed anything. There is a known issue ( https://github.com/facebook/react/issues/7047 ) about missing error / warning messages.



If you switch your test cases ("... when only id is passed" - fisrt, "... when no props are passed" is the second) and add such console.log('mockedError', console.error.mock.calls);

inside your test cases, you can see that the missing id message is not triggered in the second test.

+2


source


Given the behavior explained by @DLyman, you can do it like this:



describe('desc', () => {
    let spy = spyConsole();

    it('x', () => {
        // [...]
    });

    it('y', () => {
        // [...]
    });

    it('throws [...]', () => {
        shallow(<App />);
        expect(console.error).toHaveBeenCalled();
        expect(spy.console.mock.calls[0][0]).toContain('The prop `id` is marked as required');
    });
});

function spyConsole() {
    // https://github.com/facebook/react/issues/7047
    let spy = {};

    beforeAll(() => {
        spy.console = jest.spyOn(console, 'error').mockImplementation(() => {});
    });

    afterAll(() => {
        spy.console.mockRestore();
    });

    return spy;
}

      

+4


source


I faced a similar problem, I just needed to cache the original method

const original = console.error

beforeEach(() => {
  console.error = jest.fn()
  console.error('you cant see me')
})

afterEach(() => {
  console.log('log still works')
  console.error('you cant see me')
  console.error = original
  console.error('now you can')
})

      

+3


source


What the guys wrote above is correct. I faced similar problem and here is my solution. This also takes into account the situation when you are making some kind of statement on the mocked object:

beforeAll(() => {
    // Create a spy on console (console.log in this case) and provide some mocked implementation
    // In mocking global objects it usually better than simple 'jest.fn()'
    // because you can 'unmock' it in clean way doing 'mockRestore' 
    jest.spyOn(console, 'log').mockImplementation(() => {});
  });
afterAll(() => {
    // Restore mock after all tests are done, so it won't affect other test suites
    console.log.mockRestore();
  });
afterEach(() => {
    // Clear mock (all calls etc) after each test. 
    // It needed when you're using console somewhere in the tests so you have clean mock each time
    console.log.mockClear();
  });

      

+1


source







All Articles