Reduce boilerplate test code with Jest it.each

E.Y.
3 min readJun 27, 2020
Photo by Yulia Khlebnikova on Unsplash

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 test fn for each row.
  • name: String the title of the test block.. We can generate unique test titles by positionally injecting parameters with printf 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”],
[ButtonColour.Secondary, “color-secondary”],
[ButtonColour.Tertiary, “color-tertiary”]])
is the test data that will be passed in 3 times iterating through each item in the array; “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!

--

--