Beginners Tutorial for Beginners: Getting Started With Jest for JavaScript Testing (2019)

What is a test?

In the jargon, testing means checking that our code meets some expectation. For example, a function called Transformer may return an expected output after receiving an input.

There are many types of testing, but in general terms there are three main types of testing:

  • Unit testing
  • Integration testing
  • UI test

The Jest tutorial in this article covers unit testing, but there are many other types of testing resources at the end of the article that you can learn from.

What is Jest?

Jest is a JavaScript test library for creating, executing, and building test cases. You can install and use it as an NPM package in any project. Jest is currently the most popular test executor and is the default option when creating a React App.

How do I know what to test for?

When it comes to testing, even the simplest block of code can be overwhelming for beginners. The most frequently asked question is “How do I know what to test?” . If you’re writing a Web application, the way you test user interaction on a per-page basis is a good place to start. But Web applications are also code units composed of many functions and modules that need to be tested. There are usually two situations:

  • The legacy code you inherited didn’t write test cases
  • You have to implement a new feature from scratch

What to do? In both cases, you can write tests as part of your code. The code I’m talking about checks to see if a given function produces the desired output. A typical test flow is as follows:

  1. Introduce the function to be tested
  2. Give the function an input
  3. Define expected output
  4. Check that the function returns the expected output

That’s all. It’s not so scary to look at testing this way: input — expected output — verified results. Okay, now it’s time to introduce Jest, which is almost exactly what we’ve been talking about.

Create a project

Every JavaScript project requires an NPM environment (make sure Node is installed on your system). Next, we create a new folder and initialize the project.

mkdir getting-started-with-jest && cd The $_
npm init -y
Copy the code

Next, install Jest:

npm i jest --save-dev
Copy the code

We then configure the NPM script to be able to execute our test case from the command line. Open package.json and name the command to execute Jest “test” :

"scripts": {
  "test": "jest"
},
Copy the code

Now you can start!

Specification and test-driven development

Developers love creative freedom. But when it comes to serious matters, most of the time there aren’t that many privileges. Usually we have to follow a specification, which means a written or verbal build description.

In this tutorial, we took a fairly simple specification from the project manager. A very important client needs a function that can filter out the objects we need in an array.

For each object in the array, we check its “URL” property to see if the property value matches the given item. In the final result array, it contains all the members of the object that we matched. To become a test-proficient JavaScript developer, you need to follow the test-driven Development pattern, which requires that failed test cases be written before you can start writing code.

By default Jest looks for test files in the project folder named Tests. Let’s create a new folder:

cd getting-started-with-jest
mkdir __tests__
Copy the code

Next create a file filterbyter.spec.js in the Tests folder. Why, you may be wondering, does the file name include a “.spec “? This is actually a convention borrowed from Ruby for marking files as feature-specific specifications.

Now for the test!

Test structure & the first failed test

Ok, now we have our first Jest test case. Open the file filterbyter.spec.js and create a test block:

describe("Filter function", () => {//test stuff
});
Copy the code

Our first friend is describe, and this Jest method is used to contain one or more related tests. Each time you start writing a new test suite for a feature, package it in Describe. This method takes two parameters: a description of the test suite and a callback function that wraps the actual test case.

Next comes another function, test, which defines the actual test block:

describe("Filter function", () = > {test("it should filter by a search term (link)", () => {
    // actual test
  });
});
Copy the code

Now you’re ready to write tests. Remember, testing is about inputs, functions, and expected results. First, we define a simple input — an array of object members:

describe("Filter function", () = > {test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev"}]; }); });Copy the code

Let’s define our expected outcome. According to the specification, the function under test should remove objects whose URL attributes do not match the given search term. For example, if our search term is “link”, the expected result is an array containing only one object member:

describe("Filter function", () = > {test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev"}]; const output = [{ id: 3, url:"https://www.link3.dev" }];
  });
});
Copy the code

Now you can write the actual test code. We’ll use Jest’s Expect function and matcher to check if our hypothetical (currently) function call returns the expected result. Here is the code:

expect(filterByTerm(input, "link")).toEqual(output);
Copy the code

Or break up the code to separate out the parts that call the function:

filterByTerm(input, "link");
Copy the code

In Jest tests, we wrap test functions in Expect and use matchers (Jest functions that check input) to do the test. The complete test code is listed below:

describe("Filter function", () = > {test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev"}]; const output = [{ id: 3, url:"https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);

  });
});
Copy the code

(Check the documentation here for more information on Jest matchers)

Now, run a wave of tests:

npm test
Copy the code

You will see that the test failed:

 FAIL  __tests__/filterByTerm.spec.js
  Filter function
    ✕ it should filter by a search term (2ms)

  ● Filter functionIt should holds the filter by a search term (link) ReferenceError: filterByTerm is not defined 9 | const output = [{id: 3, url:"https://www.link3.dev" }];
      10 | 
    > 11 |     expect(filterByTerm(input, "link")).toEqual(output); 12 | | ^}); 13 |}); 14 |Copy the code

ReferenceError: filterByTerm is not defined. FilterByTerm is not defined, so let’s fix it.

Fix the test (and fail the test again)

We haven’t implemented filterByTerm yet. For convenience, we put this function definition together with our test case. Of course, in a real new project, the test cases and the functions to be tested are often for different files, and the test functions need to be imported from other files.

Inside the filterByTerm function, we need to use the native array method filter to filter out the members we need:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}
Copy the code

Here’s how it works: We check to see if the value of the “URL” property of each object member in the input array matches the regular expression in the match method. Here is the complete code:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}

describe("Filter function", () = > {test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev"}]; const output = [{ id: 3, url:"https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);
  });
});
Copy the code

Now run the test again:

npm test
Copy the code

You see that? Yes!

 PASS  __tests__/filterByTerm.spec.js
  Filter function
    ✓ it should filter by a search term (link) (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.836s, estimated 1s
Copy the code

Great. But is it over? Not yet. How do you make a function call fail again? Next, we call the following function with uppercase search terms:

function filterByTerm(inputArr, searchTerm) {
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(searchTerm);
  });
}

describe("Filter function", () = > {test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev"}]; const output = [{ id: 3, url:"https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);

    expect(filterByTerm(input, "LINK")).toEqual(output); // New test

  });
});
Copy the code

Performing tests… Well, it failed. Come on, let’s fix it again.

Fixed test: compatible with uppercase search terms

FilterByTerm should also take the uppercase search term into account. That is, even if the search is in uppercase, the corresponding matching object is returned in case-insensitive form.

filterByTerm(inputArr, "link");
filterByTerm(inputArr, "LINK");
Copy the code

To test this, we need to introduce a new test:

expect(filterByTerm(input, "LINK")).toEqual(output); // New test
Copy the code

To pass the test, we need to tweak the match method’s regular expression slightly:

//
    return arrayElement.url.match(searchTerm);
//
Copy the code

Instead of using searchTerm directly, we can build a case-insensitive regular expression. That is, an expression that is case-independent of the string. Here is the code after the fix:

function filterByTerm(inputArr, searchTerm) {
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}
Copy the code

Here is the complete test code:

describe("Filter function", () = > {test("it should filter by a search term (link)", () => {
    const input = [
      { id: 1, url: "https://www.url1.dev" },
      { id: 2, url: "https://www.url2.dev" },
      { id: 3, url: "https://www.link3.dev"}]; const output = [{ id: 3, url:"https://www.link3.dev" }];

    expect(filterByTerm(input, "link")).toEqual(output);

    expect(filterByTerm(input, "LINK")).toEqual(output);
  });
});

function filterByTerm(inputArr, searchTerm) {
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}
Copy the code

Execute here and you’ll see it pass. Fierce! As a link, you can write a new test to check the following conditions:

  1. Test if the search term is uRI
  2. Test empty search terms. How does the function handle it?

How do you build these tests?

In the next section, we’ll look at another important testing topic: code coverage.

Code coverage

What is code coverage? Before we get into that, let’s make a few tweaks to the code. Create a folder named SRC in the project root directory and create a file named filterbyter.js inside. We export this function here:

mkdir src && cd _$
touch filterByTerm.js
Copy the code

Here is the contents of the file filterbyter.js:

function filterByTerm(inputArr, searchTerm) {
  if(! searchTerm) throw Error("searchTerm cannot be empty");
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    return arrayElement.url.match(regex);
  });
}

module.exports = filterByTerm;
Copy the code

Now suppose I am a new colleague in your company. I know nothing about testing, and without knowing our development environment, I added an if statement to this function:

function filterByTerm(inputArr, searchTerm) {
  if(! searchTerm) throw Error("searchTerm cannot be empty");
  if(! inputArr.length) throw Error("inputArr cannot be empty"); // new line
  const regex = new RegExp(searchTerm, "i");
  return inputArr.filter(function(arrayElement) {
    returnarrayElement.url.match(regex); }); } module.exports = filterByTerm; dCopy the code

We added a new line of code to filterByTerm, but it was not tested. Unless I tell you “here’s a new statement to test,” you won’t know what to test. It’s almost impossible to know all the paths our code will take, so we need a tool to help us find these blind spots.

This tool is called code coverage, and it’s a powerful tool in our toolbox. Jest has a built-in code coverage tool that you can activate in two ways:

  1. Specified on the command line through the “- coverage” flag
  2. Configure this manually in package.json

Before perform coverage test, to ensure that the tests/filterByTerm. Spec. Introduces filterByTerm function in js.

const filterByTerm = require(".. /src/filterByTerm");
// ...
Copy the code

Save the file and perform coverage tests:

npm test -- --coverage
Copy the code

The following results are obtained:

 PASS  __tests__/filterByTerm.spec.js
  Filter function✓ It should filter by a search term (link) (3ms) ✓ it should filter by a search term (uRl) (1ms) ✓ it should throw when searchTerm is empty string (2ms) -----------------|----------|----------|----------|----------|-------------------| File  | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line#s |-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | All files 87.5 100 | | 75 | | 100 | | FilterByTerm. Js 87.5 | | 75 | 100 | 100 | | 3 -----------------|----------|----------|----------|----------|-------------------| Test Suites: 1 passed, 1 total Tests: 3 passed, 3 totalCopy the code

This is a good summary of our function test coverage. We see that line 3 is not overwritten. Now let’s test my new if statement to achieve 100% code coverage.

If you want to do code coverage checks every time you test, you can configure jest in package.json as follows:

"scripts": {
  "test": "jest"
},
"jest": {
  "collectCoverage": true
},
Copy the code

Or flag in the command:

"scripts": {
  "test": "jest"
},
"jest": {
  "collectCoverage": true."coverageReporters": ["html"]},Copy the code

Now, every time you run NPM test, you’ll see a folder called Coverage in your project: get-started -with-jest/coverage/. In this directory, you’ll see a bunch of files, and /coverage/index.html is the index page for these files, summarizing code coverage.

Click on the file name and you can see the exact untested lines of code:

Pretty neat. With the code coverage tool, you can know where to test the code.

How do I test React?

React is a popular JavaScript library for creating dynamic user interfaces. Jest worked fine when testing the React application (both Jest and React came from Facebook engineers). Jest is also the default test runner for creating React programs.

If you want to learn how to test The React component, check out Testing React Components: The Mostly Definitive Guide. This tutorial covers unit test components, class components, functional components with Hooks, and the new Act API.

Summary (Where to go next)

Testing is a big and fascinating topic. There are many types of tests and many test libraries to choose from. In this Jest tutorial, you learned how to configure Jest coverage reports, how to organize and write a simple unit test, and how to test JavaScript code.

If you want to learn more about UI Testing, I highly recommend you check out this Tutorial called Tutorial: JavaScript End to End Testing with Cypress.

I also recommend reading the book Test-Driven Development with Python by Harry Percival, although it’s not directly related to JavaScript. The tutorial contains all the tips and tricks for testing and provides an in-depth look at all the different types of testing.

If you are ready and want to learn about Automated Testing and Continuous Integration, the tutorial Automated Testing and Continuous Integration in JavaScript is recommended.

You can find the code for this tutorial on Github: Get-Started – with-Jest, which includes the exercise code assigned in this article.

Thanks for reading!

(after)