Testing JavaScript with Jest (2019)

Author: Valentino Gagliardi

Translator: Crazy Technology House

Original: https://www.valentinog.com/bl...

Reproduction is strictly prohibited without permission

What does testing mean?

Testing in technical terms means checking whether our code meets certain expectations. For example, given some input, a function called "transformer" should return the expected output.

There are many types of tests, and soon you'll be drowned out by terminology. Let's take a long story and write a short book. Tests fall into three categories:

  • unit testing
  • integration testing
  • UI testing

In this Jest tutorial, we will cover only unit testing, but at the end of the article, you will find more resources for other types of testing.

What is Jest?

Jest It is a JavaScript test runner, a JavaScript library for creating, running, and structuring tests. Jest is released as an NPM package and you can install it in any JavaScript project. Jest is currently one of the most popular test runners and the default choice for Create React App.

First thing to do: How do I know what to test?

When it comes to testing, even simple code blocks can paralyze beginners. The most common question is, "How do I know what to test?" If you're writing a Web application, a good starting point is to test every page and user interaction of the application. But Web applications are also made up of unit codes, such as functions and modules, which also need to be tested. In many cases, there are two situations:

  • You maintain ancestral code that is untested
  • You have to implement new functions out of thin air

What should I do? In both cases, you can check whether a given function produces the expected result by considering the code. The following is how the typical test process looks:

What should we do? In both cases, you can help yourself by treating tests as code that checks whether a given function produces the expected results. The following is how the typical test process looks:

  1. Import the function to be tested
  2. Input function
  3. Define expected output
  4. Check whether the function is output as expected

This is it. If you think in terms of these terms, testing is no longer terrible: input-expected output-assertion results. Next we'll see a handy tool for checking almost exact test content. Learn Jest now!

Setting up projects

Like every JavaScript project, you need an NPM environment (make sure Node is installed on your system). Create a new folder and initialize the project with the following commands:

mkdir getting-started-with-jest && cd $_
npm init -y

Next, install Jest:

npm i jest --save-dev

We also need to configure an NPM script to run our tests from the command line. Open package.json and configure a script named "test" to run Jest:

  "scripts": {
    "test": "jest"
  },

Specification and test-driven development

As developers, we all like creative freedom. But when it comes to serious matters, you don't have so many privileges most of the time. Usually we have to follow the norm, that is, to establish written or oral descriptions.

In this tutorial, we get a fairly simple specification from the project manager. A super important client needs a function to filter an array of objects.

For each object, we have to check the attribute named "url", and if the value of the attribute matches the given term, we should include the matched object in the result array. As a test-proficient JavaScript developer, you want to follow test-driven development, a discipline that forces you to write failed tests before you start coding.

By default, Jest wants to find the test file in a folder called tests under the project. Create a new folder:

cd getting-started-with-jest
mkdir __tests__

Next, create a new file called filterByTerm.spec.js in tests. You may wonder why the extension is ". spec.". This is a convention borrowed from Ruby to mark files as specifications for a given function.

Now let's test it.

Test structure and first failed test

Now create your first Jest test. Open filterByTerm.spec.js and create a test block:

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

Our first friend was describe, a Jest method for containing one or more related tests. Every time you start writing a new set of tests for functionality, it is included in the description block. As you can see, it requires two parameters: a string to describe the test suite and a callback function to wrap the actual test.

Next we will encounter another function called test, which is the actual test block:

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

By this time we are ready to write tests. Keep in mind that testing is about input, functionality, and expected output. First, define a simple input, an array of objects:

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" }
    ];
  });
});

Next, define the expected results. According to the specification, the function in the test should omit the object whose url attribute does not match the given search item. We can expect, for example, an array with a single object, given a "link" as a search term:

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" }];
  });
});

Now you're ready to write the actual tests. We will use expect and a Jest matcher to check the expected results returned by the function when it is called. This is the test:

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

To further refine, you can call functions in your code:

filterByTerm(inputArr, "link");

In Jest testing, you should include function calls in expect, which are actually tested with matchers (Jest functions used to check output). This is a complete test:

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);
  });
});

For more information on Jest matchers, see the documentation( https://jestjs.io/docs/en/get...)).

You can perform tests like this:

npm test

You will see that the test failed:

FAIL  __tests__/filterByTerm.spec.js
  Filter function
    ✕ it should filter by a search term (2ms)
  ● Filter function › it should 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 |

"Reference Error: filter ByTerm is not defined." In fact, this is a good thing. We'll fix it in the next section!

Repair test

What is really missing is the implementation of filterByTerm. For convenience, we will create the function in the same file where the test is located. In a real project, you need to define the function in another file and import it from the test file.

For testing, we'll use a native JavaScript function called filter, which filters out elements in the array. This is the minimum implementation of filterByTerm:

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

Here's how it works: For each element of the input array, we check the "url" attribute and match it with the regular expression using the match method. This 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);
  });
});

Run the test again:

npm test

See it pass!

 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

Very good. But have we finished the test? Not yet. What conditions do we need to fail our functions? Let's use capitalized search terms to emphasize functions:

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
  });
});

Run the test... it will fail. It's time to fix it again!

Jest Tutorial: fixing the test for uppercase

Jest Tutorial: Fixing capitalization tests

filterByTerm should also consider capitalized search terms. In other words, even if the search term is a capital string, it should return the matching object:

filterByTerm(inputArr, "link");
filterByTerm(inputArr, "LINK");

To test this situation, we introduced a new test:

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

In order for it to pass, we can adjust the regular expression provided to match:

//
    return arrayElement.url.match(searchTerm);
//

Instead of passing searchTerm directly, we can construct a case-insensitive regular expression, which means that any string matches the expression. This is a repair:

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

This is a complete test:

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);
  });
}

Run it again and see it pass. Do well! As an exercise, you need to write two new tests and check the following conditions:

  1. Test the search term "uRl"
  2. Test empty search terms. How should this function be handled?

How will you build these new tests?

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

Code coverage

What is code coverage? Before we talk about it, let's quickly adjust the code. Create a new folder named src in the project root directory, and create a file named filterByTerm.js, place and export our functions:

mkdir src && cd _$
touch filterByTerm.js

This is the file filterByTerm.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;

Now pretend I'm your new colleague. I don't know anything about testing. Instead of requiring more context, I should add a new if statement directly inside the 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) {
    return arrayElement.url.match(regex);
  });
}
module.exports = filterByTerm;

There's a new line of code in filterByTerm that doesn't seem to be tested. Unless I tell you "there's a new test statement," you won't know exactly what tests are in our functions. It's almost impossible to imagine all the paths our code can take, so a tool is needed to help uncover these blind spots.

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

  1. Pass the flag "- coverage" through the command line
  2. By configuring Jest in package.json

Before running tests with coverage, make sure that filterByTerm is imported into tests/filterByTerm.spec.js:

const filterByTerm = require("../src/filterByTerm");
// ...

Save the file and run the test with coverage:

npm test -- --coverage

That's what you got.

 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 |       75 |      100 |      100 |                   |
 filterByTerm.js |     87.5 |       75 |      100 |      100 |                 3 |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total

This is a good summary of the scope of our functional testing. As you can see, line 3 is uncovered. You can try to achieve 100% code coverage by testing the new statements I added.

If you want to keep code coverage active, configure Jest in package.json as follows:

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "collectCoverage": true
  },

You can also pass the flag to the test script:

  "scripts": {
    "test": "jest --coverage"
  },

Another way to get HTML reports with code coverage is to configure Jest as follows:

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "collectCoverage": true,
    "coverageReporters": ["html"]
  },

Now, every time you run npm test, you can access a new folder called coverage in the project folder: get-start-with-jest/coverage/. In this folder, you will find a bunch of files, where / coverage/index.html is the complete HTML summary of the code coverage.

If you click on the function name, you will also see the exact untested lines of code:

It's neat, isn't it? With code coverage, you can discover what to test when in doubt.

How to test React?

React is a very popular JavaScript library for creating dynamic user interfaces. Jest can successfully test React applications (both Jest and React come from Facebook engineers). Jest is also the default tester in Create React App.

If you want to learn how to test React components, see Testing React components: the clearest Guide . The guide covers unit test components, class components, functional components with hook s, and new ACT APIs.

Conclusion (from here on)

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

To learn more about UI testing, I strongly recommend that you check it out End-to-end testing of JavaScript with Cypress.

Even if it's not JavaScript related, I recommend reading Harry Percival's Test Driven Development Using Python . It contains tips and techniques for all testing content, and provides an in-depth introduction to all different types of testing.

If you are ready to take another step, understand automated testing and continuous integration Automated Testing and Continuous Integration in JavaScript It's for you.

You can find the code for this tutorial on Github: getting-started-with-jest And practice solutions.

Wechat Public Number: Front-end Pioneer

Welcome to scan the two-dimensional code, pay attention to the public number, and push you fresh front-end technical articles every day.

Welcome to continue reading other highly praised articles in this column:

Keywords: Javascript npm React Attribute

Added by slionheart on Mon, 02 Sep 2019 06:18:29 +0300