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.
source to share
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.
source to share
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;
}
source to share
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')
})
source to share
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();
});
source to share