【问题标题】:How to test React PropTypes through Jest?如何通过 Jest 测试 React PropTypes?
【发布时间】:2014-11-25 07:44:29
【问题描述】:

我正在为我的 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 检查似乎没有被触发。我知道检查只在开发模式下运行,但我也认为通过 npm 获取react 会给你开发版本。当我使用 watchify 构建组件时,检查会在我的浏览器中触发。

我错过了什么?

【问题讨论】:

  • 您能详细说明您正在使用的PropType 吗?我通过监视console.warn 来测试我的。
  • 对于 EventCell,我使用 number.isRequired 和 object.isRequired。我已经很长时间没有看它了,我不确定如何使用 console.warn。运行 jest 测试的 node.js 中的控制台是否可用?
  • 是的,它应该可用。
  • 使用NODE_ENV=development 运行测试能解决问题吗?

标签: reactjs jestjs reactjs-testutils react-proptypes


【解决方案1】:

对于基于 Jest 的单元测试,在您的 setup.js 上使用它会导致任何测试失败叫:

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.error 或 console.warn 进行“错误处理”(吓人的引号,因为这通常不是一个好主意)。

【讨论】:

  • 你能解释一下为什么会有 eslint-disable 评论吗?
【解决方案2】:

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

安装方式:

yarn add -D jest-prop-type-error

然后将以下内容添加到您的package.json 的setupFiles 中的jest 部分:

"setupFiles": [
  "jest-prop-type-error"
]

【讨论】:

    【解决方案3】:

    根本问题是How to test console.log?

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

    这是一个使用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)的时间戳值初始化时会引发错误。

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

    【讨论】:

    • 我认为这个答案应该被标记为接受的答案。这是一个更干净的解决方案,并提供了对浏览器的确切行为的测试。
    • 这里使用sinon 有什么原因吗? (我用过jasmine 和spyOn 似乎在规格之间没有休息)。我想知道也许某些测试框架 (sinon?) 做得更好。
    • @AdamT 我假设是因为该示例使用 Chai 作为断言库,它不像 Jasmine 那样功能齐全。我认为诗浓和茉莉之间没有显着差异。
    • 参见this other SO question/answer,了解使用console.error 的间谍/模拟测试propTypes 的一些固有限制。
    • 由于@AndrewWillems 链接的原因,此解决方案不适合测试。我刚刚发布了一个包,它提供了一个适用于测试的替代 checkPropTypes 函数:github.com/ratehub/check-prop-types
    【解决方案4】:

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

    查看 this issue on 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`.');
    

    【讨论】:

      【解决方案5】:

      简介

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

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

      总结

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

      • 模拟和清除 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(或 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);
          });
      });
      

      此方法的限制(重要)

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

      【讨论】:

      • 我认为你在 testPropTypes 函数上打错了,应该是:React.createElement(component, {...otherProps, [propName]: propValue}); ?? (通知组件而不是工具栏)
      • 你是对的。现在已经修好了。正如您可能已经猜到的那样,错字是我将自己的特定代码转换为更适合 StackOverflow 的通用代码时留下的。谢谢。
      • 我不确定有多少人这样做,但我有一个自定义验证器函数,它在内部调用PropTypes.checkPropTypes(...)。如此处所述,使用console.error = jest.fn() 是测试对我有用的验证器功能的解决方案。
      【解决方案6】:

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

      var myTestElement = TestUtils.renderIntoDocument(
      <MyTestElement height={100} /> );
      
      it("check MyTestElement props", function() {
      
         expect( typeof myTestElement.props.height ).toEqual ( 'number' );
      
      });
      

      【讨论】:

      猜你喜欢
      • 2018-08-28
      • 2019-01-07
      • 1970-01-01
      • 2018-01-25
      • 2018-05-15
      • 2022-12-14
      • 2020-07-15
      • 2022-01-23
      • 2018-02-09
      相关资源
      最近更新 更多