Alberto Gimeno Ecosystem Engineer at GitHub. Sometimes I write about JavaScript, Node.js, and frontend development.

Jest testing: Top features and how to use them

6 min read 1766

Test applications with Jest

Web developers know that testing is an important part of building applications, as it prevents regression and ensures your code is performing effectively. Using Jest as a test runner is one way to accomplish this. I’ve been a regular user of Jest for quite some time. Originally, I used it like any other test runner, but in some cases, I use it simply because it’s the default testing framework in create-react-app.

For a long time, I was not using Jest to its full potential. Now, I want to show you why I think it’s the best testing framework. Ever.

What is Jest?

Jest is a JavaScript testing framework built by Facebook and primarily designed for React-based applications, but is also used with Babel, JavaScript, Node, Angular, and Vue. It can be used to test NestJS and GraphQL, too. Let’s take a look at some of best features of Jest.

Top features of Jest

Snapshot testing in Jest

What are snapshots and why they are so handy?

The first time I saw this functionality I thought it was something limited to enzyme and react unit testing. But it’s not! Snapshot is used to track changes in UI. Snapshot testing captures code of a component at a specific time to compare it to a reference snapshot stored alongside the test. You can use snapshots for any serializable object.

Let’s take a look.

Imagine you want to test if a function returns a non-trivial value like an object with some nested data structures. I have found myself writing code like this many times:

const data = someFunctionYouAreTesting()
assert.deepEqual(data, {
  user: {
    firstName: 'Ada',
    lastName: 'Lovelace',
    email: '[email protected]'
  }
  // etc.
})

But, if some nested property is not exactly what you were expecting… You just get an error and you’ll need to find the differences visually!

assert.js:83
  throw new AssertionError(obj);
  ^

AssertionError [ERR_ASSERTION]: { user:
   { firstName: 'Ada',
     lastName: 'Lovelace!',
     email: '[email protected]' } } deepEqual { user:
   { firstName: 'Ada',
     lastName: 'Lovelace',
     email: '[email protected]' } }

If the function you are testing returns something random (for example, when you generate a random API key) you cannot use this mechanism anymore. In that case, you have to manually check field by field:

const data = someFunctionYouAreTesting()
assert.ok(data.user)
assert.equal(data.user.firstName, 'Ada')
assert.equal(data.user.lastName, 'Lovelace')
assert.equal(data.user.email, '[email protected]')
// and it goes on...

This is better from a testing perspective, but it’s a lot more work.

We made a custom demo for .
No really. Click here to check it out.

If you find yourself doing these things, you’ll love snapshots!

You will write something like this:

const data = someFunctionYouAreTesting()
expect(data).toMatchSnapshot()

…and the first time the test runs, Jest will store the data structure in a snapshot file that you can manually open and validate. Any time you run the test again Jest will load the snapshot and compare it with the received data structure from the test. If there are any differences, Jest will print a colored diff to the output. Awesome!

Jest testing snapshot data structure

Now, what if we don’t want to compare the whole structure (because some fields can be dynamic or can change from test to test)? No problem.

const data = someFunctionYouAreTesting()
expect(data).toMatchSnapshot({
  createdAt: expect.any(Date),
  id: expect.any(Number),
})

These are called property matchers .

But there’s more. One problem I found with this way of validating data structures is that the snapshot file is separated from the test code. So sometimes you need to jump from one file to another to check that the snapshot contains what you are expecting. No problem! If the snapshot is small enough you can use inline snapshots. You just need to use:

const data = someFunctionYouAreTesting()
expect(data).toMatchInlineSnapshot()

And that’s it! Wait… but where’s the snapshot?

The snapshot is not there… yet. The first time you run the test, Jest will accept the data structure and instead of storing it in a snapshot file it will put it in your code.

Yeah, it will change your testing code, resulting in something like this:

const { someFunctionYouAreTesting } = require("../src/app");
test("hello world", () => {
  const data = someFunctionYouAreTesting();
  expect(data).toMatchInlineSnapshot(`
Object {
  "user": Object {
    "email": "[email protected]",
    "firstName": "Ada",
    "lastName": "Lovelace",
  },
}
`);
});

This blows my mind, and I love it. A development tool seamlessly changing your code is a simple and elegant solution that would be super useful in other scenarios. Imagine having a react/angular/vue development mode where you can visually edit components in the browser and the code is updated for you to match those changes.

By the way, if the test is not small enough to use inline snapshots you can still get some help. If you use Visual Studio Code with this extension you can see the snapshot on hover (it’s very useful even though it has some limitations).

Using interactive mode

In the beginning, I thought the interactive mode was just a fancy term for the typical watch feature that many CLI applications have. But then I learned a few things.

Jest integrates with Git and Mercurial. By default, the watch mode will run only the tests affected by the changes made since the last commit. This is cool and makes me write more atomic commits, too. If you are wondering how Jest knows what tests are affected by commit changes, you are not alone.

The first thing Jest does is load the tests and thus load the source code of your application, parsing the requires() and imports to generate a graph of interdependencies.

But using Git or Mercurial is not the only thing you can do to limit the number of tests to run every time. When I make changes to the source code and I see many failed tests, I focus on the simplest test that fails. You can do that by using test.only, but there’s a better way (I especially don’t like test.only or test.skip because it’s easy to forget about it and leave it in your code).

The “interactive way” is more elegant and convenient. Without editing your test code you can limit the tests to run in different ways.

Let’s take a look.

The simplest one is by hitting t and entering the name of the test. If you have test hello world, click t, write, “hello world” and hit enter.

Jest testing interactive mode

Well, that works in most cases, if you have a test (hello world 2), it will run, too, because you entered a regular expression. To avoid this, I usually add $ at the end of the pattern.

In projects where there are many integration tests that hit the database, I found that running the tests was still slow. Why?

Filtering by test name does not prevent all of the before() and after() callbacks to be run in all of the other tests. And usually, in integration tests, those callbacks are where you put heavy stuff like opening and closing connections to the database.

So, in order to prevent that I usually also filter by file name. Just hit p (for path) and enter the filename that contains the test. You’ll find that the test runs a lot faster now (to return just click t and clean the filter by hitting enter. Do the same with the filter for file names with p and enter).

Another super handy feature is upgrading. When you see the diff and you see that the new snapshot is fine and the old one is outdated, just hit u (for upgrade) and the snapshot will be overwritten!

jest testing interactive mode upgrade

Two more useful options are a to run all the tests and f to run the failed tests again.

Out-of-the-box ease

Another thing that I like is that Jest is a “batteries included” framework, meaning you usually don’t have to add plugins or libraries to add common functionality to it. It just ships with it! Some examples:

  • Add coverage when invoking Jest to get coverage reports from your tests with the ability to choose between a few built-in reporters or custom ones. You can even set a coverage threshold so your tests (and your CI) fails if that threshold is not met. This is perfect for keeping up a good test coverage in your code
  • Add notify and you’ll receive desktop notifications when the test runner finishes. If you have thousands of tests, they can take a while to finish. You’ll save time just by adding this flag
  • You don’t need to add an assertion library to your project in order to start writing powerful and useful assertions. You have an extensive expect functionality already built-in, ready to be used with interesting functionality such as the colored diffing that we also saw in the snapshot functionality
  • You don’t need a library to mock functions or services. You have plenty of utilities to mock functions and modules and check how they were invoked

Debugging Jest tests with VSCode

Debugging Jest tests with VSCode is pretty straightforward.

Just go to the debugger tab and click the gear icon with the little red dot. Click on it and create a Node.js launch file. Now replace the content with the recipe you’ll find below.

This recipe is based on a separate recipe, which includes two configurations: one for running all tests and one for running just the current test file. I’ve added an additional configuration that allows you to select (in the text editor) the name of a test and run just that one.

I’ve also added the watch flag so you can edit your code or your test, save it, and the test will re-run very quickly. This is possible because Jest loads the test by itself, not the underlying system (the Node.js runtime).

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Jest All",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["--runInBand"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "windows": {
        "program": "${workspaceFolder}/node_modules/jest/bin/jest",
      }
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Jest Current File",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["${relativeFile}"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "windows": {
        "program": "${workspaceFolder}/node_modules/jest/bin/jest",
      }
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Jest Selected Test Name",
      "program": "${workspaceFolder}/node_modules/.bin/jest",
      "args": ["${relativeFile}", "-t=${selectedText}$", "--watch"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen",
      "windows": {
        "program": "${workspaceFolder}/node_modules/jest/bin/jest",
      }
    }
  ]
}

Conclusions

Despite what many may think, Jest is not just a test runner—it is a complete testing framework that has brought testing to another level. It’s powerful but easy to use, so give it a try. I promise you won’t regret it.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Alberto Gimeno Ecosystem Engineer at GitHub. Sometimes I write about JavaScript, Node.js, and frontend development.

One Reply to “Jest testing: Top features and how to use them”

Leave a Reply