Be Smart, Write Parameterized Tests with Jest!
Tags: testing, jest, javascript, typescript
Published: 2021-04-08 13:09:09

Have you ever feel like that extra `it` or `test` block you've just added inside your `describe` test block, ***looks exactly like the one you wrote before*** ? 🤔

Recently, while unit-testing one of my latest OSS projects ( [@nxrocks/nx-spring-boot](https://github.com/tinesoft/nxrocks/tree/master/packages/nx-spring-boot): a plugin to generate and integrate a `Spring Boot` project inside a `Nx` workspace), I asked myself that same question.

## My use case

I needed to test that my plugin was working perfectly for the two build systems available for Spring Boot projects (`Maven` and `Gradle`), as well as for generating either `application` or `library` projects.

I had already tested the correct generation for Maven-based  `applications`:
{% gist https://gist.github.com/tinesoft/bcfdbebc50dd690448c026bb4de2a27e file=jest-maven-based-application-test.ts %}

So 3 more combinations remained to be tested:

* Generation of Maven-based `libraries`
* Generation of Gradle-based `applications`
* and Generation of Gradle-based `libraries`

## The naive solution: duplicated test blocks

So at first, I copy-pasted that first test to make the others three. The complete test suite was:

{% gist https://gist.github.com/tinesoft/bcfdbebc50dd690448c026bb4de2a27e file=jest-non-parameterized-tests.ts %}

It was working fine of course... but felt **terribly wrong**. The 4 tests are looking almost exactly the same, except for a few parameters that change. 
I wasn't comfortable with this duplication as it makes refactoring harder and gives a huge kick in the n**ts🌰 to the <abbr title="Don't Repeat Yourself">DRY</abbr> principle.
 
Coming from a `Java` background, I was aware of a technique called **parameterized testing** with `Junit` (the most used testing library in `Java`), which allows executing a **single test method multiple times with different parameters**.

That was exactly what was needed here. So I asked myself: 
> does the same kind of library exist in `Javascript`? 

A quick search on Google later, I found what I was looking for: [jest-each](https://github.com/facebook/jest/tree/master/packages/jest-each)

> A parameterised testing library for [Jest](https://jestjs.io/) inspired by [mocha-each](https://github.com/ryym/mocha-each).

## The optimal solution: parameterized tests!

`jest-each` (which is already integrated into the Jasmine's [`it`/`test`](https://jestjs.io/docs/api#testeachtablename-fn-timeout)/[`describe`](https://jestjs.io/docs/api#describeeachtablename-fn-timeout) provided by Jest, so no additional installation required) allows you to provide multiple arguments to your `test`/`describe` blocks, which results in the test/suite being run once per row of parameters.

This is how the previous test suite could be rewritten:

{% gist https://gist.github.com/tinesoft/bcfdbebc50dd690448c026bb4de2a27e file=jest-parameterized-tests.ts%}

Soooo much better right?! Short and concise 😉.

## How does it work?

Let's break it down a little bit:

1 - In the above code block (lines `4-9`), using a [tagged template](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) provided by the library, we can express the parameters of our test, in this cool [table-like](https://github.com/facebook/jest/tree/master/packages/jest-each#tagged-template-literal-of-rows) style.

```typescript
it.each`
    projectType      | buildSystem         | buildFile         | wrapperName
    ${'application'} | ${'maven-project'}  | ${'pom.xml'}      | ${'mvnw'}
    ${'application'} | ${'gradle-project'} | ${'build.gradle'} | ${'gradlew'}
    ${'library'}     | ${'maven-project'}  | ${'pom.xml'}      | ${'mvnw'}
    ${'library'}     | ${'gradle-project'} | ${'build.gradle'} | ${'gradlew'}
`
```
> **Note**: The first row expresses the parameters, the following rows express the different values they can take

2 - In the `test`'s (or `it`'s) description, we can reference the parameters (the headers in the above table) using the syntax `$paramName`, which allows a dynamic test description.

3 - We reference the parameters in our test body, by providing them as an `object parameter` of our test function (`{ projectType, buildSystem, buildFile, wrapperName }`).
```typescript
("should download a spring boot '$projectType' build with '$buildSystem'", async ({ projectType, buildSystem, buildFile, wrapperName }) => {
  // your expectations here...
}
```

That's it!

Happy Testing! 👩🏾‍💻👨🏾‍💻