【问题标题】:Jest: mocking console.error - tests fails开玩笑:模拟 console.error - 测试失败
【发布时间】:2017-11-19 16:24:34
【问题描述】:

问题:

我有一个简单的 React 组件,用于学习使用 Jest 和 Enzyme 测试组件。在使用道具时,我添加了prop-types 模块来检查开发中的属性。 prop-types 使用 console.error 在未传递强制 props 或 props 的数据类型错误时发出警报。

我想模拟console.error 来计算prop-types 在我传递缺失/错误类型的道具时调用它的次数。

使用这个简化的示例组件和测试,我希望这两个测试的行为如下:

  1. 第一个测试需要 0/2 的 props 应该捕获两次模拟调用。
  2. 具有 1/2 所需道具的第二个测试应该捕获一次调用的模拟。

相反,我得到了这个:

  1. 第一个测试运行成功。
  2. 第二次测试失败,抱怨 mock 函数被调用了 0 次。
  3. 如果我交换测试顺序,第一个有效,第二个失败。
  4. 如果我将每个测试拆分为一个单独的文件,那么两者都可以。
  5. console.error 的输出被抑制了,所以很明显它对两者都被嘲笑了。

我确定我遗漏了一些明显的东西,比如清除模拟错误或其他什么。

当我对导出函数的模块使用相同的结构,调用console.error 任意次数时,一切正常。

当我用酶/反应测试时,我在第一次测试后碰到了这堵墙。

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

示例 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);
});

最后说明: 是的,最好编写组件以在缺少道具时生成一些用户友好的输出,然后对其进行测试。但是一旦我发现了这种行为,我就想弄清楚我做错了什么,以此来提高我的理解力。显然,我错过了一些东西。

【问题讨论】:

    标签: javascript reactjs jestjs enzyme


    【解决方案1】:

    我也遇到了类似的问题,只需要缓存原来的方法

    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')
    })
    

    【讨论】:

    • 您不需要缓存原始方法。你可以做console.error.mockRestore()
    • 有意思,下次遇到这个我试试
    • mockRestore 在这种情况下不起作用。来自 jest 24 文档:“请注意,mockFn.mockRestore 仅在使用 jest.spyOn 创建模拟时才有效。因此,在手动分配 jest.fn() 时,您必须自己处理恢复。”
    • 经典! . . . .
    【解决方案2】:

    鉴于@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;
    }
    

    【讨论】:

      【解决方案3】:

      上面写的都是对的。我遇到了类似的问题,这是我的解决方案。当您对模拟对象进行一些断言时,它还会考虑情况:

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

      【讨论】:

      • 我发现调用 mockImplementationOnce 很有用,可以避免恢复模拟。
      【解决方案4】:

      你没有错过任何东西。有一个关于丢失错误/警告消息的已知问题 (https://github.com/facebook/react/issues/7047)。

      如果你切换你的测试用例('...当只有 id 被通过'-第一个,'...当没有道具通过'-第二个)并添加这样的 console.log('mockedError', console.error.mock.calls); 在您的测试用例中,您可以看到,关于缺少 id 的消息在第二个测试中没有被触发。

      【讨论】:

        【解决方案5】:

        对于我的解决方案,我只是包装原始控制台并将所有消息组合到数组中。可能是需要它的人。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-06-16
          • 2018-02-13
          • 2020-10-08
          • 2021-03-12
          • 2018-07-20
          • 2020-08-19
          • 2021-05-02
          • 2017-02-11
          相关资源
          最近更新 更多