Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jest: mocking console.error - tests fails

The Problem:

I have a simple React component I'm using to learn to test components with Jest and Enzyme. As I'm working with props, I added the prop-types module to check for properties in development. prop-types uses console.error to alert when mandatory props are not passed or when props are the wrong data type.

I wanted to mock console.error to count the number of times it was called by prop-types as I passed in missing/mis-typed props.

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

  1. The first test with 0/2 required props should catch the mock calling twice.
  2. The second test with 1/2 required props should catch the mock called once.

Instead, I get this:

  1. The first test runs successfully.
  2. The second test fails, complaining that the mock function was called zero times.
  3. If I swap the order of the tests, the first works and the second fails.
  4. If I split each test into an individual file, both work.
  5. console.error output is suppressed, so it's clear it's mocked for both.

I'm sure I am missing something obvious, like clearing the mock wrong or whatever.

When I use the same structure against a module that exports a function, calling console.error some arbitrary number of times, things work.

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

Sample App.js:

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
};

Sample App.test.js

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: Yeah, it's better to write the component to generate some user friendly output when props are missing, then test for that. But once I found this behavior, I wanted to figure out what I'm doing wrong as a way to improve my understanding. Clearly, I'm missing something.

like image 896
Matthew Bakaitis Avatar asked Jun 16 '17 19:06

Matthew Bakaitis


3 Answers

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

const original = console.error

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

afterEach(() => {
  console.error('you cant see me')
  console.error = original
  console.error('now you can')
})
like image 162
lfender6445 Avatar answered Oct 18 '22 08:10

lfender6445


Given the behavior explained by @DLyman, you could do it like that:

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

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

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

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

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

    it('throws [...]', () => {
        shallow(<App />);
        expect(console.error).toHaveBeenCalled();
        expect(console.error.mock.calls[0][0]).toContain('The prop `id` is marked as required');
    });
});
like image 21
Mike Gleason jr Couturier Avatar answered Oct 18 '22 09:10

Mike Gleason jr Couturier


What guys wrote above is correct. I've encoutered similar problem and here's my solution. It takes also into consideration situation when you're doing some assertion 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's 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's needed when you're using console somewhere in the tests so you have clean mock each time
    console.log.mockClear();
  });
like image 16
Papi Avatar answered Oct 18 '22 09:10

Papi