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:
- Import the function to be tested
- Input function
- Define expected output
- 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:
- Test the search term "uRl"
- 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:
- Pass the flag "- coverage" through the command line
- 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:
- Deep Understanding of Shadow DOM v1
- Step by step teach you how to use WebVR to implement virtual reality games
- Thirteen Modern CSS Frameworks to Improve your Development Efficiency
- Fast Start Bootstrap Vue
- How does the JavaScript engine work? Everything you need to know from call stack to Promise
- WebSocket Reality: Real-time Communication between Node and React
- 20 interview questions about Git
- In-depth analysis of Node.js console.log
- What exactly is Node.js?
- Build an API server with Node.js in 30 minutes
- Object Copy of Javascript
- Programmers earn less than 30K a month before they are 30 years old. Where to go?
- 14 Best JavaScript Data Visualization Libraries
- Eight top-level VS Code extension plug-ins for the front end
- Complete guide to Node.js multithreading
- Four Solutions and Implementation of Converting HTML to PDF