I'm writing Jest tests for my React code and hoping to make use of/test the PropType checks. I am quite new to the Javascript universe. I'm using npm to install react-0.11.2
and have a simple:
var React = require('react/addons');
In my tests. My test looks quite similar to the jest/react tutorial example with code like:
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 ');
However it seems that the PropType checks in the EventCell
component aren't being triggered. I understand that the checks are only run in Development mode but then I also thought that getting react
through npm gave you the development version. The checks trigger in my browser when I build the component with watchify.
What am I missing?
Jest is a JavaScript testing framework that allows developers to run tests on JavaScript and TypeScript code and can be easily integrated with React JS. Open the package. json, and you will find that when you use create-react-app for creating a react project, it has default support for jest and react testing library.
Testing React Hooks with Jest and Enzyme. Jest and Enzyme are tools used for testing React apps. Jest is a JavaScript testing framework used to test JavaScript apps, and Enzyme is a JavaScript testing utility for React that makes it easier to assert, manipulate, and traverse your React components' output.
PropTypes are simply a mechanism that ensures that the passed value is of the correct datatype. This makes sure that we don't receive an error at the very end of our app by the console which might not be easy to deal with.
Before React 15.5. 0, proptypes are available in the React package, but in later versions of React, you need to add a dependency to your project. You can add a dependency to your project using the below command: npm install prop-types --save.
The underlying problem is How to test console.log
?
The short answer is that you should replace the console.{method}
for the duration of the test. The common approach is to use spies. In this particular case, you might want to use stubs to prevent the output.
Here is an example implementation using Sinon.js (Sinon.js provides standalone spies, stubs and mocks):
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();
});
});
In this example DataName
component will throw an error when initialised with a timestamp value that does not represent a precise date (12:00:00 AM).
I am stubbing the console.error
method (This is what Facebook warning
module is using internally to generate the error). I ensure that the stub has been called once and with exactly one parameter representing the error.
Intro
The answer by @Gajus definitely helped me (so, thanks Gajus). However, I thought I would provide an answer that:
Summary
Like the approach suggested here by Gajus and elsewhere by others, the basic approach I'm suggesting is also to determine whether or not console.error
is used by React in response to an unacceptable test prop value. Specifically, this approach involves doing the following for each test prop value:
console.error
(to ensure prior calls to console.error
aren't interfering),console.error
was fired as expected.The testPropTypes
Function
The following code can be placed either within the test or as a separate imported/required module/file:
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);
};
Calling the Function
Any test examining propTypes
can call testPropTypes
using three or four parameters:
component
, the React component that is modified by the prop;propName
, the string name of the prop under test;arraysOfTestValues
, an array of arrays of all the desired test values of the prop to be tested:
optionally, otherProps
, an object containing prop name/value pairs for any other required props of this component.
The otherProps
object is needed to ensure React doesn't make irrelevant calls to console.error
because other required props are inadvertently missing. Simply include a single acceptable value for any required props, e.g. {requiredPropName1: anyAcceptableValue, requiredPropName2: anyAcceptableValue}
.
Function Logic
The function does the following:
It sets up a mock of console.error
which is what React uses to report props of incorrect type.
For each sub-array of test prop values provided it loops through each test prop value in each sub-array to test for prop type:
Within the loop for each individual test prop value, the console.error
mock is first cleared so that any error messages detected can be assumed to have come from this test.
An instance of the component is then created using the test prop value as well as any other necessary required props not currently being tested.
Finally, a check is made to see whether a warning has been triggered, which should happen if your test tried to create a component using an inappropriate or missing prop.
Testing for Optional versus Required Props
Note that assigning null
(or undefined
) to a prop value is, from React's perspective, essentially the same thing as not providing any value for that prop. By definition this is acceptable for an optional prop but unacceptable for a required one. Thus, by placing null
in either the array of acceptable or unacceptable values you test whether that prop is optional or required respectively.
Example Code
MyComponent.js (just the 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);
});
});
Limitation of This Approach (IMPORTANT)
There are currently some significant limitations on how you can use this approach which, if over-stepped, could be the source of some hard-to-trace testing bugs. The reasons for, and implications of, these limitations are explained in this other SO question/answer. In summary, for simple prop types, like for myProp1
, you can test as many unacceptable non-null
test prop values as you want as long as they are all of different data types. For some complex prop types, like for myProp2
, you can only test a single unacceptable non-null
prop value of any type. See that other question/answer for a more in-depth discussion.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With