简体   繁体   English

Jest:mocking console.error - 测试失败

[英]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. 我有一个简单的React组件,我用它来学习用Jest和Enzyme测试组件。 As I'm working with props, I added the prop-types module to check for properties in development. 当我使用道具时,我添加了prop-types模块来检查开发中的属性。 prop-types uses console.error to alert when mandatory props are not passed or when props are the wrong data type. prop-types使用console.error来警告何时未传递强制道具或者道具是错误的数据类型。

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. 我想模拟console.error来计算prop-types调用它的次数,因为我传递了丢失/错误输入的道具。

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. 使用0/2所需道具的第一个测试应该捕获模拟调用两次。
  2. The second test with 1/2 required props should catch the mock called once. 使用1/2必需道具的第二次测试应该捕获一次调用的模拟。

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. console.error输出被抑制,所以很明显它被嘲笑了。

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. 当我对导出函数的模块使用相同的结构时,调用console.error一些任意次数,事情就行了。

It's when I test with enzyme/react that I hit this wall after the first test. 当我用酶/反应测试时,我在第一次测试后撞到了这堵墙。

Sample App.js: 示例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 示例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. 显然,我错过了一些东西。

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.log('log still works')
  console.error('you cant see me')
  console.error = original
  console.error('now you can')
})

Given the behavior explained by @DLyman, you could do it like that: 鉴于@DLyman解释的行为,你可以这样做:

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

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

You didn't miss anything. 你没有错过任何东西。 There is a known issue ( https://github.com/facebook/react/issues/7047 ) about missing error/warning messages. 关于缺少错误/警告消息存在一个已知问题( https://github.com/facebook/react/issues/7047 )。

If you switch your test cases ('...when only id is passed' - the fisrt, '...when no props are passed' - the second) and add such console.log('mockedError', console.error.mock.calls); 如果你切换你的测试用例('...只传递id' - fisrt,'......当没有传递道具'时 - 第二个)并添加这样的console.log('mockedError', console.error.mock.calls); inside your test cases, you can see, that the message about missing id isn't triggered in the second test. 在您的测试用例中,您可以看到,在第二次测试中未触发有关缺少id的消息。

For my solutions I'm just wrapping original console and combine all messages into arrays. 对于我的解决方案,我只是包装原始控制台并将所有消息组合成数组。 May be someone it will be needed. 可能是需要它的人。

const mockedMethods = ['log', 'warn', 'error']
export const { originalConsoleFuncs, consoleMessages } = mockedMethods.reduce(
  (acc: any, method: any) => {
    acc.originalConsoleFuncs[method] = console[method].bind(console)
    acc.consoleMessages[method] = []

    return acc
  },
  {
    consoleMessages: {},
    originalConsoleFuncs: {}
  }
)

export const clearConsole = () =>
  mockedMethods.forEach(method => {
    consoleMessages[method] = []
  })

export const mockConsole = (callOriginals?: boolean) => {
  const createMockConsoleFunc = (method: any) => {
    console[method] = (...args: any[]) => {
      consoleMessages[method].push(args)
      if (callOriginals) return originalConsoleFuncs[method](...args)
    }
  }

  const deleteMockConsoleFunc = (method: any) => {
    console[method] = originalConsoleFuncs[method]
    consoleMessages[method] = []
  }

  beforeEach(() => {
    mockedMethods.forEach((method: any) => {
      createMockConsoleFunc(method)
    })
  })

  afterEach(() => {
    mockedMethods.forEach((method: any) => {
      deleteMockConsoleFunc(method)
    })
  })
}


声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM