Navigate back to the homepage
📚 Books

Testing JavaScript Applications with Jest

Mark Pollmann
June 23rd, 2018 · 3 min read

Introduction

Writing tests for JavaScript applications can be quite fun and easy if you find a tool you like. In this post I will talk about my favorite way to test React.js applications, namely the Jest Framework.

Before you even start writing tests

Consider using static type-checking with a tool like Flow or Typescript in your project. This catches a lot of compile-time bugs. Also setting up a linter like ESLint is worth a lot!

Testing Overview

Some general pointers before we get started:

  • Make tests visible by putting them close to the file you’re testing (at least unit tests). Don’t let them wither and die in some dark corner of your app directory.
  • Test like your software is used, don’t test implementation details. Think about what steps the user wants to accomplish and mimic him as close as possible.

The Jest Framework

Jest was created by Facebook and runs all their tests, including React apps. If it’s good enough for them it should be good enough for us.
It runs tests in parallel and doesn’t touch the DOM by running in Nodejs (utilizing JSDOM). Those features make it really fast! It runs failed tests first so you get quick feedback, comes with a nice watch mode, an ability to run snapshot tests and a powerful mocking library.

Setting it up

If you use create-react-app Jest comes pre-installed along with a npm script to run it (npm run test). Generating a code coverage overview is as simple as running jest --coverage. If you’re not using cra it can be a bit tricky to get set up for the first time. As I’m not an expert on this I will leave this to others to write about, a google search should find some good posts.

Writing unit tests

Jest finds your tests automatically, as long as they are in a folder called __tests__ or the filenames themselves contain .test or .spec.
Some example file names: register.test.js or somecomponent.spec.js.

watch mode

Jest’s watch mode is super nice and lets you filter your tests in ways so only the tests you want will be running. This is the interface you see when it runs in watch mode:

1› Press o to only run tests related to changed files.
2 › Press p to filter by a filename regex pattern.
3 › Press t to filter by a test name regex pattern.
4 › Press q to quit watch mode.

Mocking with Jest

Mocking out dependencies (like API calls) is very powerful but contains quite some magic in Jest. This excellent post by Rick Hanlon II is a must-read.

Mocking Function with jest.fn()

The simplest mocking function can be created with this function. It creates a function returning undefined but collects data about how it was called. Let’s say you want to test a React login form component that takes a handleSubmit prop function that usually calls an API to log your user in after the user filled out the login form and pressed the submit button. We just want to test if this component works, not the API, so we mock it out:

1const myMock = jest.fn();
2render(<Login onSubmit={myMock} />);
3expect(handleSubmit).toHaveBeenCalledTimes(1);
4expect(handleSubmit).toHaveBeenCalledWith(/* the correct form input */);

jest.fn() can also take a callback if you want your mock to return something

1const myMock = jest.fn(() => true);
2myMock(); // returns true

Mocking modules with jest.mock()

Let’s say you use aws-amplify’s Auth module to log your users in. You can mock the whole Auth module by running jest.mock('aws-amplify') somewhere in your test (jest.mock calls get hoisted to the top so it doesn’t interfere with imports). Now jest looks for a aws-amplify.js file in a __mocks__ folder at the same level as your node_modules. In it we have this:

1// in __mocks__/aws-amplify.js
2export const Auth = {
3 currentSession: jest.fn(() => Promise.resolve()),
4 signIn: jest.fn(() => Promise.resolve()),
5 signOut: jest.fn(() => Promise.resolve()),
6};

Now all calls to one of these three functions of the aws-amplify.Auth module just return a resolved Promise and you can use the same assertion functions like

1expect(Amplify.Auth.signIn).toHaveBeenCalled();

Spying with jest.spyOn()

If you want to have these handy assertions like .toHaveBeenCalled() or other but would like to leave the original implementation in place, this function is for you. Use it like this:

1// Math.add exists
2const mySpy = jest.spyOn("Math", "add");
3Math.add(1, 2); // 3
4expect(mySpy).toHaveBeenCalledWith(1, 2);

So Math.add() gets runs normally when it’s spied on! This can trip some people up coming from other testing libraries.

Snapshot Testing

Snapshots are a serialization of the dom to check if your UI has not changed unexpectedly. They are quite easily generated like this (example from the Jest docs):

1import React from "react";
2import Link from "../Link.react";
3import renderer from "react-test-renderer";
4
5it("renders correctly", () => {
6 const tree = renderer
7 .create(<Link page="http://www.facebook.com">Facebook</Link>)
8 .toJSON();
9 expect(tree).toMatchSnapshot();
10});

While this sounds at first like the solution to everything, they are not without problems. To get a better understand read Effective Snapshot Testing by Kent C. Dodds.

Additional tools

I’m a big fan of Kent C. Dodds’ react-testing-library which aims to be a replacement to Airbnb’s Enzyme. I will probably write a separate post specifically about testing with this library in the future.

If there’s something missing or you would like me to write about contact me via email or twitter!

More articles from Mark Pollmann

Notes on Jason Cohen's Talk at MicroConf

A great talk on the ideal startup.

February 6th, 2018 · 3 min read

Understanding Redux Middleware by understanding redux-thunk

There has always been the notion that Redux middleware is some kind of black magic but the idea behind it is actually quite simple and…

January 31st, 2018 · 1 min read
© 2019 Mark Pollmann
Link to $https://twitter.com/mark_pollmannLink to $https://github.com/MarkPollmannLink to $https://www.linkedin.com/in/mark-pollmann-961446132/