DEV Community

Bartosz Gordon
Bartosz Gordon

Posted on

Simplify repetitive Jest test cases with test.each

Problem

From time to time, I run into a situation, where most of my test cases follow a similar sequence of steps. This scenario most often happens while unit testing helpers/utility functions. Given certain arguments, check if the actual result is equal to the expected result. Over and over again. As the number of cases grows, the test suite can get bloated.

Contrived example ahead:

const add = (a, b) => a + b;

describe("'add' utility", () => {
  it("given 2 and 2 as arguments, returns 4", () => {
    const result = add(2, 2);
    expect(result).toEqual(4);
  });
  it("given -2 and -2 as arguments, returns -4", () => {
    const result = add(-2, -2);
    expect(result).toEqual(-4);
  });
  it("given 2 and -2 as arguments, returns 0", () => {
    const result = add(2, -2);
    expect(result).toEqual(0);
  });
});

Enter fullscreen mode Exit fullscreen mode

Solution

I thought about an abstraction to avoid this kind of boilerplate, and after a few google searches, I found the test.each Jest utility.

This helper encourages you to create the array of cases, where you store arguments and expected results, and then iterate through the entire array to run the tested function and assert the results.

Example with test.each:

const add = (a, b) => a + b;

const cases = [[2, 2, 4], [-2, -2, -4], [2, -2, 0]];

describe("'add' utility", () => {
  test.each(cases)(
    "given %p and %p as arguments, returns %p",
    (firstArg, secondArg, expectedResult) => {
      const result = add(firstArg, secondArg);
      expect(result).toEqual(expectedResult);
    }
  );
});

Enter fullscreen mode Exit fullscreen mode

Notes

Benefits:

  • easier to add new tests cases
  • less boilerplate

Possible drawback:

  • more abstractions, some people may find it unnecessary

I find it worthwhile to write a comment about the items of the cases array to increase readability and reduce mental effort.

  // first argument, second argument, expected result
  const cases = [[2, 2, 4], [-2, -2, -4], [2, -2, 0]];
Enter fullscreen mode Exit fullscreen mode

Top comments (16)

Collapse
 
eddsaura profile image
Jose E Saura

Hey I really liked this example.

Is there a way to make something similar but the difference would be in 1 test the element is in the document and the other test the element is NOT in the document.

I kinda have an idea but I think what I thought is too much abstraction and unnecesary.

Collapse
 
harishrajora12 profile image
Harish Rajora

Great article on simplifying Jest test cases! It's always helpful to explore different approaches to streamline testing processes. Speaking of testing, I recently came across a comprehensive guide on running Jest Testing with Selenium and JavaScript. It covers everything from setup to execution and provides valuable insights. If anyone is interested in expanding their testing capabilities, I highly recommend checking it out. how to run Jest Testing with Selenium and JavaScript Keep up the excellent content!"

Collapse
 
makaveli1313 profile image
makaveli1313

Thank you, so much easier to understand then in the docs!

Collapse
 
makaveli1313 profile image
makaveli1313

Any idea of how to use just the second argument in the text?
"given %p and %p as arguments, returns %p",

Collapse
 
targumon profile image
Amnon Sadeh • Edited
  1. Change the order of the arguments so the previously second becomes first.
  2. Use the "Tagged Template Literal" variant as described in the docs: jestjs.io/docs/en/api#testeachtabl...
Collapse
 
nonary profile image
Nonary

You'll have to use both of them, but the convention is if you're not using a parameter you should name it a single underscore _

Collapse
 
ekhro97 profile image
Joel Coll

What is the benefit of using the test.each of jest instead of js for each? I think it is more readable and friendly the js way

Collapse
 
mpeloquin profile image
Maxime Péloquin

Using test.each will create one test for each test case, so if it fails, you will know exactly which test case failed. It will also run the rest of the test cases even if one failed.

If you use for each, then your entire test will fail on the first case failure, and you will not know which one failed quickly.

Collapse
 
usersaurus profile image
Antonio Luis Román

That's not completely true. You can put an it inside a forEach loop and get the same behavior.

Collapse
 
sammykapienga profile image
sammy kapienga

This was just a nice article.Thanks so much Gordon

Collapse
 
danielhofficial profile image
Daniel H.

How do you deal with cases where you have over 100,000 possible combinations you want to test out?

Collapse
 
venkat4541 profile image
Vk

Thanks. Simple use case helped grasp the concept quickly.

Collapse
 
rflagreca profile image
Roberto La Greca • Edited

Really nice solution. Thanks.

Collapse
 
machy44 profile image
machy44

this is exactly what I needed. tnx for the article

Collapse
 
isaachagoel profile image
Isaac Hagoel

thanks. this was helpful

Collapse
 
kurtisask profile image
kurtisas-k

Thank-you! I'm just beginning to implement tests today, I've known about them for years, but only dabbled. And finally now I have a use! And a great tutorial thanks to you.