Recently I have found a nice trick when use Jest with React — it.each
.
There are times when we need to write similar test code (for unit tests especially) following the same pattern like:
given a expect b
given c expect d
given e expect f
And each argument and expected return has same/similar formats.
It is tedious and time-consuming to write this kind of tests, when all you need to do is to pass in different data for same test. But luckily, in Jest you can reduce all these tests into one simple test:
test.each(table)(name, fn, timeout)
There are two ways to use this method:
- it.each(table)(name, fn)
- it.each`table`(name, fn)
1. test.each(table)(name, fn, timeout)
table
:Array
of Arrays with the arguments that are passed into the testfn
for each row.name
:String
the title of the test block.. We can generate unique test titles by positionally injecting parameters withprintf
formatting:%p
- pretty-format.|%s
- String.|%d
- Number. |%i
- Integer.|%f
- Floating point value.|%j
- JSON.|%o
- Object.%#
- Index of the test case.|%%
- single percent sign ('%').fn
:Function
the test to be ran, this is the function that will receive the parameters in each row as function arguments.- Optionally, you can provide a
timeout
(in milliseconds) for specifying how long to wait for each row before aborting.
Example:
test.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('.add(%i, %i)', (a, b, expected) => {
expect(a + b).toBe(expected);
});//or it.each([
[ButtonColour.Primary, “color-primary”],
[ButtonColour.Secondary, “color-secondary”],
[ButtonColour.Tertiary, “color-tertiary”]])
(
“should, when colour prop is %s, set class %s”,
(colour, colourClass) => {
const button = shallow(
<CallToActionLink href=”#” colour={colour as ButtonColour} /> );
expect(button.prop(“className”)).toEqual( `size-medium ${colourClass} anchor` );
});
In the example above, [ButtonColour.Primary, “color-primary”],
is the test data that will be passed in 3 times iterating through each item in the array;
[ButtonColour.Secondary, “color-secondary”],
[ButtonColour.Tertiary, “color-tertiary”]])“should, when colour prop is %s, set class %s”,
is the test title that is printed based on printf
formatting for each test; colour, colourClass
are the arguments corresponding to the items in the array e.g. ButtonColour.Primary
and “color-primary”
.
2. test.each`table`(name, fn, timeout)
table
:Tagged Template Literal
: First row of variable name column headings separated with|
; One or more subsequent rows of data supplied as template literal expressions using${value}
syntax.name
:String
the title of the test, use$variable
to inject test data into the test title from the tagged template expressions.fn
:Function
the test to be ran, this is the function that will receive the test data object.- Optionally, you can provide a
timeout
(in milliseconds) for specifying how long to wait for each row before aborting.
Example:
test.each`
a | b | expected
${1} | ${1} | ${2}
${1} | ${2} | ${3}
${2} | ${1} | ${3}
`('returns $expected when $a is added $b', ({a, b, expected}) => {
expect(a + b).toBe(expected);
});//ordescribe('test Button component', () => {
it.each`
colour | colourClass
${ButtonColour.Primary} | ${“color-primary”}
${ButtonColour.Secondary} | ${“color-secondary”}
${ButtonColour.Tertiary} | ${“color-tertiary”}
`('should, when colour prop is $colour set class $colourClass', ({colour, colourClass}) => {
const button = shallow(
<CallToActionLink href=”#” colour={colour as ButtonColour} />
);
expect(button.prop(“className”)).toEqual( `size-medium ${colourClass} anchor` );
});
As we can compare, the differences in this way are :
- Instead of passing an array, it passes in a table using string interpolation(if you use cucumber test library you might be familiar with this style)
- test title is named using tagged template expressions
- passing in argument in
{}
as objects
In summary, I prefer both methods when using test.each
, but there’s a small learning curve to get head around it. However, once you master it, it makes writing repetitive tests so much easier!