Testing JavaScript code with Jest for dummies. Part 1

Hello, Habr! This guide is the first part in a planned series of articles about such a great testing framework like Jest. The material will be useful for beginners and those who are just getting acquainted with testing, and would like to study this framework. In the first part we will analyze: how to get started with jest, how to write a simple test, and what methods are there for comparing the values ​​to be checked against the expected ones. Who cares - welcome to kat!

What is Jest?


As indicated on the project home page:
Jest is an amazing JavaScript testing environment with a focus on simplicity.
And indeed, Jest is very simple. It does not require additional settings, easy to understand and use, and also has pretty good documentation. Great for projects using Node, React, Angular, Vue, Babel, TypeScript and more.
It is also open source and supported by Facebook.

Installation


To install Jest in your project, do:

npm install --save-dev jest

If you are using yarn:

yarn add --dev jest

After installation, you can update the scripts section of your package.json:

β€œscripts” : {
     β€œtest”: β€œjest”
}

With this simple call, we can already run our tests (in fact, jest will require at least one test to exist).

You can also install globally (but I would not recommend doing this, since for me the global installation of modules is bad practice):

npm install jest --global

And accordingly for yarn:

yarn global add jest

After that, you can use jest directly from the command line.

By calling the jest --init command in the root of the project, after answering a few questions, you will get the jest.config.js settings file. Or you can add the configuration directly to your package.json. To do this, add the "jest" key to the json root and in the corresponding object you can add the settings you need. We will analyze the options themselves later. This is not necessary at this stage, since jest can be used immediately, without additional configurations.

First test


Let's create the first.test.js file and write our first test:

//first.test.js
test('My first test', () => {
    expect(Math.max(1, 5, 10)).toBe(10);
});

And run our tests using npm run test or directly with the jest command (if installed globally). After launch, we will see a report on passing the tests.

 <b>PASS</b>  ./first.test.js
  βœ“ My first test (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.618 s, estimated 1 s

Let's break our test and run jest again:

//first.test.js
test('My first test', () => {
    expect(Math.max(1, 5, 10)).toBe(5);
});

As we can see, now our test does not pass the test. Jest displays detailed information about where the problem occurred, what the expected result was, and what we got instead.

Now let's analyze the code of the test itself. Test functionused to create a new test. It takes three arguments (in the example we used a call with two arguments). The first is a line with the name of the test, its jest will display in the report. The second is a function that contains the logic of our test. You can also use the 3rd argument - timeout. It is optional, and its default value is 5 seconds. It is set in milliseconds. This parameter is necessary when we work with asynchronous code and return a promise from the test function. It indicates how long jest must wait for the promise to resolve. After this time, if the promise was not allowed - jest will consider the test failed. More information about working with asynchronous calls will be in the following parts. Alternatively, you can use it () instead of test ().. There is no difference between such calls. it () is just an alias on the test () function .

Inside the test function, we first call expect () . To him we pass the value we want to check. In our case, this is the result of calling Math.max (1, 5, 10) . expect () returns a wrapper object that has a number of methods for comparing the resulting value with the expected one. One of these methods we used is toBe .

Let's look at the main of these methods:

  • toBe() β€” , , . Object.is(). === 0 -0, NaN c NaN.
  • toEqual() β€” , . . . .

    test('toEqual with objects', () => {
        expect({ foo: 'foo', subObject: { baz: 'baz' } })
            .toEqual({ foo: 'foo', subObject: { baz: 'baz' } });  // 
        expect({ foo: 'foo', subObject: { num: 0 } })
            .toEqual({ foo: 'foo', subObject: { baz: 'baz' } });  //    .
    });
    
    test('toEqual with arrays', () => {
        expect([11, 19, 5]).toEqual([11, 19, 5]); // 
        expect([11, 19, 5]).toEqual([11, 19]); // 
    });
    

  • toContain() β€” . ===.

    const arr = ['apple', 'orange', 'banana'];
    expect(arr).toContain('banana');
    expect(new Set(arr)).toContain('banana');
    expect('apple, orange, banana').toContain('banana');
    

  • toContainEqual() β€” .

    expect([{a: 1}, {b: 2}]).toContainEqual({a: 1});
    

  • toHaveLength() β€” length .

    expect([1, 2, 3, 4]).toHaveLength(4);
    expect('foo').toHaveLength(3);
    expect({ length: 1 }).toHaveLength(1);
    

  • toBeNull() β€” null.
  • toBeUndefined() β€” undefined.
  • toBeDefined() β€” toBeUndefined. !== undefined.
  • toBeTruthy() β€” true. false, null, undefined, 0, NaN .
  • toBeFalsy() β€” toBeTruthy(). false.
  • toBeGreaterThan() toBeGreaterThanOrEqual() β€” , >, >=.
  • toBeLessThan() toBeLessThanOrEqual() β€” toBeGreaterThan() toBeGreaterThanOrEqual()
  • toBeCloseTo() β€” , , . .

    const num = 0.1 + 0.2; // 0.30000000000000004
    expect(num).toBeCloseTo(0.3);
    expect(Math.PI).toBeCloseTo(3.14, 2);
    

  • toMatch() β€” .

    expect('Banana').toMatch(/Ba/);
    

  • toThrow () - used when it is necessary to check the exception. You can check the fact of the error itself, or check for throwing exceptions of a certain class, either by the error message, or by the correspondence of the message to the regular expression.

    function funcWithError() {
        throw new Error('some error');
    }   
    expect(funcWithError).toThrow();
    expect(funcWithError).toThrow(Error);
    expect(funcWithError).toThrow('some error');
    expect(funcWithError).toThrow(/some/);
    

  • not - this property allows you to check for Inequality. It provides an object that has all the methods listed above, but they will work the other way around.

    expect(true).not.toBe(false);
    expect({ foo: 'bar' }).not.toEqual({});
    
    function funcWithoutError() {}
    expect(funcWithoutError).not.toThrow();
    

Let's write a couple of simple tests. First, create a simple module that will contain several methods for working with circles.

// src/circle.js
const area = (radius) => Math.PI * radius ** 2;
const circumference = (radius) => 2 * Math.PI * radius;
module.exports = { area, circumference };

Next, add the tests:

// tests/circle.test.js
const circle = require('../src/circle');

test('Circle area', () => {
    expect(circle.area(5)).toBeCloseTo(78.54);
    expect(circle.area()).toBeNaN();
});

test('Circumference', () => {
    expect(circle.circumference(11)).toBeCloseTo(69.1, 1);
    expect(circle.circumference()).toBeNaN();
});

In these tests, we checked the result of 2 methods - area and circumference . Using the toBeCloseTo method , we checked the expected result. In the first case, we checked or the calculated area of ​​the circle with a radius of 5 is approximately equal to 78.54, while the difference with the obtained value (it will be 78.53981633974483) is not large and the test will be counted. In the second, we indicated that we are interested in checking to within 1 decimal place. We also called our methods with no arguments and checked the result using toBeNaN . Since the result of their execution will be NaN, then the tests will be passed successfully.

Let's take another example. Let's create a function that will filter an array of products by price:

// src/productFilter.js
const byPriceRange = (products, min, max) =>
         products.filter(item => item.price >= min && item.price <= max);
module.exports = { byPriceRange };

And add a test:

// tests/product.test.js
const productFilter = require('../src/producFilter');

const products = [
    { name: 'onion', price: 12 },
    { name: 'tomato', price: 26 },
    { name: 'banana', price: 29 },
    { name: 'orange', price: 38 }
];

test('Test product filter by range', () => {
    const FROM = 15;
    const TO = 30;
    const filteredProducts = productFilter.byPriceRange(products, FROM, TO);

    expect(filteredProducts).toHaveLength(2);
    expect(filteredProducts).toContainEqual({ name: 'tomato', price: 26 });
    expect(filteredProducts).toEqual([{ name: 'tomato', price: 26 }, { name: 'banana', price: 29 }]);
    expect(filteredProducts[0].price).toBeGreaterThanOrEqual(FROM);
    expect(filteredProducts[1].price).toBeLessThanOrEqual(TO);
    expect(filteredProducts).not.toContainEqual({ name: 'orange', price: 38 });
});

In this test, we will check the result of the byRangePrice function . First, we checked the correspondence of the length of the resulting array to the expected one - 2. The next check requires that the element contain the element - {name: 'tomato', price: 26}. The object in the array and the object passed toContainEqual are two different objects, not a reference to the same one. But toContainEqual will compare each property. Since both objects are identical, the verification will be successful. Next, we use toEqual to check the structure of the entire array and its elements. The toBeGreaterThanOrEqual and toBeLessThanOrEqual methods will help us check the price of the first and second element of the array. And finally, calling not.toContainEqualwill check if the element is contained in the array - {name: 'orange', price: 38}, which, by condition, should not be there.

In these examples, we wrote some simple tests using the verification functions described above. In the following parts, we will discuss work with asynchronous code, the jest functions that were not covered in this part of the tutorial, we will talk about its configuration, and much more.

All Articles