开玩笑:模拟 console.error - 测试失败
Posted
技术标签:
【中文标题】开玩笑:模拟 console.error - 测试失败【英文标题】:Jest: mocking console.error - tests fails 【发布时间】:2017-11-19 16:24:34 【问题描述】:问题:
我有一个简单的 React 组件,用于学习使用 Jest 和 Enzyme 测试组件。在使用道具时,我添加了prop-types
模块来检查开发中的属性。 prop-types
使用 console.error
在未传递强制 props 或 props 的数据类型错误时发出警报。
我想模拟console.error
来计算prop-types
在我传递缺失/错误类型的道具时调用它的次数。
使用这个简化的示例组件和测试,我希望这两个测试的行为如下:
-
第一个测试需要 0/2 的 props 应该捕获两次模拟调用。
具有 1/2 所需道具的第二个测试应该捕获一次调用的模拟。
相反,我得到了这个:
-
第一个测试运行成功。
第二次测试失败,抱怨 mock 函数被调用了 0 次。
如果我交换测试顺序,第一个有效,第二个失败。
如果我将每个测试拆分为一个单独的文件,那么两者都可以。
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);
);
最后说明: 是的,最好编写组件以在缺少道具时生成一些用户友好的输出,然后对其进行测试。但是一旦我发现了这种行为,我就想弄清楚我做错了什么,以此来提高我的理解力。显然,我错过了一些东西。
【问题讨论】:
【参考方案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)
)
)
【讨论】:
以上是关于开玩笑:模拟 console.error - 测试失败的主要内容,如果未能解决你的问题,请参考以下文章
开玩笑的测试在调用模拟的 firebase 函数之前完成,因此失败