34

我正在为我的 React 代码编写 Jest 测试,并希望利用/测试 PropType 检查。我对 Javascript 世界很陌生。我正在使用 npm 安装react-0.11.2并有一个简单的:

var React = require('react/addons');

在我的测试中。我的测试看起来与 jest/react 教程示例非常相似,代码如下:

var eventCell = TestUtils.renderIntoDocument(
  <EventCell
    slot={slot}
    weekId={weekId}
    day={day}
    eventTypes={eventTypes}
    />
);

var time = TestUtils.findRenderedDOMComponentWithClass(eventCell, 'time');
expect(time.getDOMNode().textContent).toEqual('19:00 ');

但是,似乎EventCell没有触发组件中的 PropType 检查。我知道检查只在开发模式下运行,但后来我也认为react通过 npm 给了你开发版本。当我使用 watchify 构建组件时,检查会在我的浏览器中触发。

我错过了什么?

4

6 回答 6

40

根本问题是如何测试console.log

简短的回答是您应该console.{method}在测试期间更换 。常用的方法是使用间谍。在这种特殊情况下,您可能希望使用存根来阻止输出。

这是一个使用Sinon.js的示例实现(Sinon.js 提供独立的间谍、存根和模拟):

import {
    expect
} from 'chai';
import DateName from './../../src/app/components/DateName';
import createComponent from './create-component';
import sinon from 'sinon';

describe('DateName', () => {
    it('throws an error if date input does not represent 12:00:00 AM UTC', () => {
        let stub;

        stub = sinon.stub(console, 'error');

        createComponent(DateName, {date: 1470009600000});

        expect(stub.calledOnce).to.equal(true);
        expect(stub.calledWithExactly('Warning: Failed propType: Date unix timestamp must represent 00:00:00 (HH:mm:ss) time.')).to.equal(true);

        console.error.restore();
    });
});

在此示例DataName中,当使用不代表精确日期(12:00:00 AM)的时间戳值初始化时,组件将引发错误。

我正在对console.error方法进行存根(这是 Facebookwarning模块在内部用来生成错误的方法)。我确保存根已被调用一次,并且只有一个参数代表错误。

于 2015-08-05T14:23:03.840 回答
11

介绍

@Gajus 的回答绝对帮助了我(所以,谢谢 Gajus)。但是,我想我会提供一个答案:

  • 使用更新的 React (v15.4.1)
  • 使用Jest(React 自带)
  • 允许为单个道具测试多个道具值
  • 通用

概括

就像 Gajus 和其他人在这里建议的方法一样,我建议的基本方法也是确定 React 是否console.error使用它来响应不可接受的 test prop value。具体来说,这种方法涉及对每个测试道具值执行以下操作:

  • 模拟和清除console.error(以确保之前的调用console.error不会干扰),
  • 使用正在考虑的测试道具值创建组件,以及
  • 确认是否console.error按预期被解雇。

testPropTypes功能_

以下代码可以放置在测试中,也可以作为单独的导入/必需模块/文件放置:

const testPropTypes = (component, propName, arraysOfTestValues, otherProps) => {
    console.error = jest.fn();
    const _test = (testValues, expectError) => {
        for (let propValue of testValues) {
            console.error.mockClear();
            React.createElement(component, {...otherProps, [propName]: propValue});
            expect(console.error).toHaveBeenCalledTimes(expectError ? 1 : 0);
        }
    };
    _test(arraysOfTestValues[0], false);
    _test(arraysOfTestValues[1], true);
};

调用函数

任何测试propTypes都可以使用三个或四个参数调用testPropTypes

  • component,被 prop 修改的 React组件;
  • propName,被测道具的字符串名称;
  • arraysOfTestValues,要测试的道具 的所有所需测试值的数组数组:
    • 第一个子数组包含所有可接受的测试道具值,而
    • 第二个子数组包含所有不可接受的测试道具值;和
  • 可选,otherProps一个对象,其中包含该组件的任何其他必需道具的道具名称/值对。

    需要该otherProps对象来确保 React 不会console.error因为其他必需的 props 无意中丢失而进行不相关的调用。只需为任何所需的道具包含一个可接受的值,例如{requiredPropName1: anyAcceptableValue, requiredPropName2: anyAcceptableValue}.

功能逻辑

该函数执行以下操作:

  • 设置了一个模拟,console.error React 使用该模拟来报告不正确类型的道具。

  • 对于每个测试道具值的子数组,它会遍历每个子数组中的每个测试道具值以测试道具类型:

    • 两个子数组中的第一个应该是可接受的测试道具值列表。
    • 第二个应该是不可接受的测试道具值
  • 在每个单独的测试道具值的循环中,首先console.error清除模拟,以便可以假定检测到的任何错误消息来自该测试。

  • 然后使用测试道具值以及当前未测试的任何其他必要的必需道具创建组件的实例。

  • 最后,检查是否已触发警告,如果您的测试尝试使用不适当或缺少的道具创建组件,则应该发生这种情况。

测试可选道具和必需道具

请注意,从 React 的角度来看,将null(or undefined) 分配给 prop 值与不为该 prop 提供任何值本质上是一样的。根据定义,这对于可选道具是可以接受的,但对于必需道具是不可接受的。因此,通过将null可接受或不可接受的值放入数组中,您可以分别测试该道具是可选的还是必需的

示例代码

MyComponent.js(只是propTypes):

MyComponent.propTypes = {
    myProp1: React.PropTypes.number,      // optional number
    myProp2: React.PropTypes.oneOfType([  // required number or array of numbers
        React.PropTypes.number,
        React.PropTypes.arrayOf(React.PropTypes.number)
    ]).isRequired

MyComponent.test.js:

describe('MyComponent', () => {

    it('should accept an optional number for myProp1', () => {
        const testValues = [
            [0, null],   // acceptable values; note: null is acceptable
            ['', []] // unacceptable values
        ];
        testPropTypes(MyComponent, 'myProp1', testValues, {myProp2: 123});
    });

    it('should require a number or an array of numbers for myProp2', () => {
        const testValues = [
            [0, [0]], // acceptable values
            ['', null] // unacceptable values; note: null is unacceptable
        ];
        testPropTypes(MyComponent, 'myProp2', testValues);
    });
});

这种方法的局限性(重要)

目前,如何使用这种方法存在一些重大限制,如果过度使用,可能会导致一些难以追踪的测试错误。这些限制的原因和影响在另一个 SO question/answer中进行了解释。总之,对于简单的 prop 类型,例如 for myProp1,您可以根据需要测试尽可能多的不可接受的非null测试 prop 值,只要它们都是不同的数据类型。对于某些复杂的 prop 类型,例如 for myProp2,您只能测试任何类型的单个不可接受的非nullprop 值。请参阅其他问题/答案以进行更深入的讨论。

于 2017-01-27T01:34:46.547 回答
7

Mockingconsole.error不适合在单元测试中使用!@AndrewWillems 在上面的评论中链接到另一个 SO 问题,描述了这种方法的问题。

在 facebook/prop-types 上查看这个问题,以讨论该库抛出而不是记录 propType 错误的能力(在撰写本文时,它不受支持)。

我已经发布了一个辅助库来同时提供这种行为,check-prop-types。你可以像这样使用它:

import PropTypes from 'prop-types';
import checkPropTypes from 'check-prop-types';

const HelloComponent = ({ name }) => (
  <h1>Hi, {name}</h1>
);

HelloComponent.propTypes = {
  name: PropTypes.string.isRequired,
};

let result = checkPropTypes(HelloComponent.propTypes, { name: 'Julia' }, 'prop', HelloComponent.name);
assert(`result` === null);

result = checkPropTypes(HelloComponent.propTypes, { name: 123 }, 'prop', HelloComponent.name);
assert(`result` === 'Failed prop type: Invalid prop `name` of type `number` supplied to `HelloComponent`, expected `string`.');
于 2017-07-06T20:17:10.370 回答
5

一个新的包jest-prop-type-error很容易添加并且在PropType错误时失败:

通过以下方式安装:

yarn add -D jest-prop-type-error

然后将以下内容添加到您package.json的部分setupFilesjest

"setupFiles": [
  "jest-prop-type-error"
]
于 2018-08-08T16:22:01.660 回答
1

由于 ReactJS 只会向控制台发送警告,但实际上不会抛出错误,因此我以这种方式测试 prop 值:

var myTestElement = TestUtils.renderIntoDocument(
<MyTestElement height={100} /> );

it("check MyTestElement props", function() {

   expect( typeof myTestElement.props.height ).toEqual ( 'number' );

});
于 2015-06-11T21:02:41.967 回答
1

对于基于 Jest 的单元测试,在您的(prop-type errors) 或(React compat issues, like still using ) 最终被调用的setup.js任何测试中使用它都会失败:console.errorconsole.warncomponentWillUpdate

beforeEach(() => {
  jest.spyOn(console, 'error')
  jest.spyOn(console, 'warn')
})

afterEach(() => {
  /* eslint-disable no-console,jest/no-standalone-expect */
  expect(console.error).not.toBeCalled()
  expect(console.warn).not.toBeCalled()
})

当任何测试调用时,这都会中断jest.restoreAllMocks()- 对我们来说,调用 jest.clearAllMocks() 反而会有所帮助。

它还要求您的应用程序不要调用console.errorconsole.warn进行“错误处理”(引号,因为这通常不是一个好主意)。

于 2020-04-17T11:51:18.767 回答