Back

Reduce repeated code in Jest tests with it.each

Kishan Gajera
Kishan Gajera
March 31, 2022
Reduce repeated code in Jest tests with it.each

When writing tests, I often find myself repeating test cases with only minor changes. A common example of this is when testing different values for a form or API endpoint and expecting certain validation errors.

Below is an example of what I'm referring to. These are two tests that are testing a GraphQL mutation to log a user in.

describe('invalid email', () => { it('returns an Authentication error', async () => { await UserFactory.create({ email: 'foo@wee.net' }); const variables = { email: 'fake', password: 'fake' }; const response = await graphQLRequest({ query, variables }); const errorMessages = response.body.errors.map((e: GraphQLError) => e.message); expect(errorMessages[0]).toBe("No user found for email: fake"); }); }); describe('invalid password', () => { it('returns an Authentication error', async () => { const user = await UserFactory.create({ email: 'foo@wee.net' }); const variables = { email: user.email, password: 'fake' }; const response = await graphQLRequest({ query, variables }); const errorMessages = response.body.errors.map((e: GraphQLError) => e.message); expect(errorMessages[0]).toBe("Invalid password"); }); });

Both of these tests do the following:

  1. Creating a user in the database
  2. Make GraphQL request for the login mutation
  3. Check the error messages returned from the server

The only difference between the two tests are the variables passed to the GraphQL mutation and the expected error messages.

Another approach to writing the tests to reduce code repetition is using it.each. This allows us to write the test once and loop over it using different data in each iteration. Using this approach, our tests could be rewritten like this:

it.each([ { variables: { email: 'fake' }, error: 'No user found for email: fake' }, { variables: { password: 'fake' }, error: 'Invalid password' }, ]) ('returns error: $error', async ({ variables, error }) => { const user = await UserFactory.create({ email: 'foo@wee.net' }); const response = await graphQLRequest({ query, variables: { email: user.email, password: 'fake' , ...variables, } }); const errorMessages = response.body.errors.map((e: GraphQLError) => e.message); expect(errorMessages[0]).toBe(error); });

Now, we have an array of test cases passed to it.each that specify the variables to use in the GraphQL mutation and the expected error message. This is the array that will be iterated through. So, our test will run for each element and the element will be accessible in our test.

You should also notice that we're using 'returns error: $error' for the title of the test. The $error references the error property in our test case objects. We do this to give each test case a unique title so if it fails, we know which case caused the failure.

We can easily add more test cases without needing to copy and paste. In this example, it might also be good to test when the email or password are undefined. To do this, we would just add the following elements to the array:

[ { variables: { email: undefined }, error: 'Email is required' }, { variables: { password: undefined }, error: 'Password is required' }, ]

I hope you saw how using this approach, you reduce repeating code and also can cover more ground with your test cases!

Share this post

Interested in working with us?

Give us some details about your project, and our team will be in touch within a day or two.