# Jest 教程

> 标题： [http://zetcode.com/javascript/jest/](http://zetcode.com/javascript/jest/)

Jest 教程展示了如何使用 Jest 框架在 JavaScript 应用中执行单元测试。

## Jest

Jest JavaScript REST 框架，其重点是简单性。 Jest 是由 Facebook 工程师为其 React 项目创建的。

单元测试是一种软件测试，其中测试软件的各个单元（组件）。 单元测试的目的是验证软件的每个单元是否按设计执行。 单元是所有软件中最小的可测试部分。

模拟是一种技术，其中代码部分被模拟真实代码的虚拟实现所替代。 模拟有助于实现测试隔离。 模拟主要用于单元测试。

在我们的测试中，我们检查值是否满足某些条件。 `expect()`函数为我们提供了许多匹配器，这些匹配器使我们可以验证不同的事物，例如`toBe()`，`toBeFalsy()`或`toEqual()`。

在本教程中，我们在 Node 应用中使用 Jest。

## 安装 Jest

首先，我们安装 Jest。

```js
$ node -v
v11.5.0

```

我们使用 Node 版本 11.5.0。

```js
$ npm init -y

```

我们启动一个新的 Node 应用。

```js
$ npm i --dev jest

```

我们用`nmp i --dev jest`安装 Jest 模块。

```js
$ npm i -g jsonserver
$ npm i axios

```

我们也将使用`jsonserver`和`axios`。

## package.json

测试脚本运行`jest`。

`package.json`

```js
{
  "name": "jest-test",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "jest --verbose"
  },
  "keywords": [],
  "author": "Jan Bodnar",
  "license": "ISC",
  "devDependencies": {
    "jest": "^24.0.0"
  },
  "dependencies": {
    "axios": "^0.18.0"
  }
}

```

缺省情况下，笑话仅提供基本输出。 要获得有关测试运行的更多信息，我们使用`--verbose`标志。

## Jest 运行测试

使用`npm test`命令运行测试。 测试文件的名称中必须带有`test`术语。

```js
$ npm test

> jest-test@1.0.0 test C:\Users\Jano\Documents\js\jest-test
> jest

  PASS  ./math-utils.test.js
  PASS  ./arith.test.js
  PASS  ./arith-params.test.js
  PASS  ./arith-skip.test.js
  PASS  ./string-utils.test.js
  PASS  ./arith-mock.test.js
  PASS  ./users.test.js

Test Suites: 7 passed, 7 total
Tests:       2 skipped, 35 passed, 37 total
Snapshots:   0 total
Time:        5.19s
Ran all test suites.

```

这是使用 Jest 运行测试的示例输出。 这是一个简洁的输出。 有关更多信息，我们可以使用`--verbose`选项。

要运行单个测试，我们可以使用`npx jest testname`命令。

```js
scripts:{
    "test": "jest --verbose ./test-directory"
}

```

我们可以配置 Jest 在指定的测试目录中运行测试。

## 用 Jest 测试算术函数

以下是使用 Jest 降级单元测试的经典学术示例。

`arith.js`

```js
const add = (a, b) => a + b;
const mul = (a, b) => a * b;
const sub = (a, b) => a - b;
const div = (a, b) => a / b;

module.exports = { add, mul, sub, div };

```

一个模块中有四个基本算术函数。

`arith.test.js`

```js
const { add, mul, sub, div } = require('./arith');

test('2 + 3 = 5', () => {
  expect(add(2, 3)).toBe(5);
});

test('3 * 4 = 12', () => {
  expect(mul(3, 4)).toBe(12);
});

test('5 - 6 = -1', () => {
  expect(sub(5, 6)).toBe(-1);
});

test('8 / 4 = 2', () => {
  expect(div(8, 4)).toBe(2);
});

```

在`arith.test.js`中，我们测试模块。 文件名包含测试词。 然后由玩笑挑出来。

```js
test('2 + 3 = 5', () => {
  expect(add(2, 3)).toBe(5);
});

```

我们使用`test()`函数测试`add()`方法。 第一个参数是测试的名称，第二个参数是要运行的函数。 我们正在测试`add()`函数返回的样本数据正确答案。

```js
$ npx jest arith.test.js
PASS  ./arith.test.js
  √ 2 + 3 = 5 (3ms)
  √ 3 * 4 = 12 (6ms)
  √ 5 - 6 = -1
  √ 8 / 4 = 2 (1ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        10.981s
Ran all test suites matching /arith.test.js/i.

```

这是输出。

## Jest 跳过测试

测试可能需要花费大量时间才能完成。 如果需要，我们可以跳过一些测试。

`arith-skip.test.js`

```js
const { add, mul, sub, div } = require('./arith');

xtest('2 + 3 = 5', () => {
  expect(add(2, 3)).toBe(5);
});

test.skip('3 * 4 = 12', () => {
  expect(mul(3, 4)).toBe(12);
});

test('5 - 6 = -1', () => {
  expect(sub(5, 6)).toBe(-1);
});

test('8 / 4 = 2', () => {
  expect(div(8, 4)).toBe(2);
});

```

可以使用`skip()`或使用`x`前缀跳过测试。 在我们的例子中，前两个测试被跳过。

```js
$ npx jest arith-skip.test.js
PASS  ./arith-skip.test.js
  √ 5 - 6 = -1 (2ms)
  √ 8 / 4 = 2 (1ms)
  ○ skipped 2 tests

Test Suites: 1 passed, 1 total
Tests:       2 skipped, 2 passed, 4 total
Snapshots:   0 total
Time:        2.323s
Ran all test suites matching /arith-skip.test.js/i.

```

跳过了两个测试。

## Jest 参数化测试

参数化测试允许我们使用不同的值多次运行相同的测试。 这使我们的测试功能更强大。

对于参数化测试，我们使用`each()`全局函数。

`arith-param.test.js`

```js
const { add, mul, sub, div } = require('./arith')

test.each([[1, 1, 2], [-1, 2, 1], [2, 1, 3]])(
  '%i + %i equals %i', (a, b, expected) => {
    expect(add(a, b)).toBe(expected);
  },
);

test.each([[1, 1, 0], [-1, 2, -3], [2, 2, 0]])(
  '%i - %i equals %i', (a, b, expected) => {
    expect(sub(a, b)).toBe(expected);
  },
);

test.each([[1, 1, 1], [-1, 2, -2], [2, 2, 4]])(
  '%i * %i equals %i', (a, b, expected) => {
    expect(mul(a, b)).toBe(expected);
  },
);

test.each([[1, 1, 1], [-1, 2, -0.5], [2, 2, 1]])(
  '%i / %i equals %i', (a, b, expected) => {
    expect(div(a, b)).toBe(expected);
  },
);

```

在这些测试中，我们使用不同的输入数据多次运行每个算术函数。

```js
test.each([[1, 1, 2], [-1, 2, 1], [2, 1, 3]])(
  '%i + %i equals %i', (a, b, expected) => {
    expect(add(a, b)).toBe(expected);
  },
);

```

`each()`方法接收一个数组数组，该数组的参数将传递给每一行的测试函数。 `%i`是需要整数的格式说明符。 这是针对`--verbose`选项显示的输出的。

```js
$ npx jest arith-params.test.js
PASS  ./arith-params.test.js
  √ 1 + 1 equals 2 (3ms)
  √ -1 + 2 equals 1 (1ms)
  √ 2 + 1 equals 3
  √ 1 - 1 equals 0
  √ -1 - 2 equals -3
  √ 2 - 2 equals 0
  √ 1 * 1 equals 1 (1ms)
  √ -1 * 2 equals -2
  √ 2 * 2 equals 4
  √ 1 / 1 equals 1 (1ms)
  √ -1 / 2 equals 0
  √ 2 / 2 equals 1

Test Suites: 1 passed, 1 total
Tests:       12 passed, 12 total
Snapshots:   0 total
Time:        1.759s
Ran all test suites matching /arith-params.test.js/i.

```

这是输出。

## Jest

`beforeAll()`函数是测试设置的一部分。 它会在运行此文件中的任何测试之前运行一个函数。 如果函数返回一个`Promise`或是一个生成器，则 Jest 在运行测试之前等待该`Promise`解析。

`math-utils.js`

```js
const sum = (vals) => {

    let sum = 0;

    vals.forEach((val) => {
        sum += val;
    });

    return sum;
}

const positive = (vals) => {

    return vals.filter((x) => { return x > 0; });
}

const negative = (vals) => {

    return vals.filter((x) => { return x < 0; });
}

module.exports = { sum, positive, negative };

```

我们有一个`math-utils`模块，其中包含三个函数：`sum()`，`positive()`和`negative()`。

`math-utils.test.js`

```js
const { sum, positive, negative } = require('./math-utils');

let vals;
let sum_of_vals;
let pos_vals;
let neg_vals;

beforeAll(() => {
    pos_vals = [2, 1, 3];
    neg_vals = [-2, -1, -1];
    vals = pos_vals.concat(neg_vals);
    sum_of_vals = vals.reduce((x, y) => x + y, 0);
})

test('the sum of vals should be 2', () => {
    expect(sum(vals)).toBe(sum_of_vals);
});

test('should get positive values', () => {
    expect(positive(vals)).toEqual(pos_vals);
});

test('should get negative values', () => {
    expect(negative(vals)).toEqual(neg_vals);
});

```

在测试文件中，我们使用`beforeAll()`函数的测试运行之前初始化测试数据。

```js
test('should get positive values', () => {
    expect(positive(vals)).toEqual(pos_vals);
});

```

为了测试`positive()`函数，我们使用`toEqual()`匹配器。 我们测试该函数返回的正值数组等于预定义的测试值数组。

## Jest 分组测试

在 Jest 中，测试使用`describe()`分组为单位。 它创建一个将几个相关测试组合在一起的模块。

`string-utils.js`

```js
const isPalindrome = (string) => string == string.split('').reverse().join('');

const isAnagram = (w1, w2) => {

    const regularize = (word) => {
        return word.toLowerCase().split('').sort().join('').trim();
    }

    return regularize(w1) === regularize(w2);
}

module.exports = {isPalindrome, isAnagram};

```

我们的`string-utils.js`模块具有两个函数：`isPalindrome()`和`isAnagram()`。

`math-utils.js`

```js
const sum = (vals) => {

    let sum = 0;

    vals.forEach((val) => {
        sum += val;
    });

    return sum;
}

const positive = (vals) => {

    return vals.filter((x) => { return x > 0; });
}

const negative = (vals) => {

    return vals.filter((x) => { return x < 0; });
}

module.exports = { sum, positive, negative };

```

我们又有了`math-utils.js`模块。

`groups.test.js`

```js
const { sum, positive, negative } = require('./math-utils');
const { isPalindrome, isAnagram } = require('./string-utils');

describe('testing math utilities', () => {
    let vals;
    let sum_of_vals;
    let pos_vals;
    let neg_vals;

    beforeAll(() => {
        pos_vals = [2, 1, 3];
        neg_vals = [-2, -1, -1];
        vals = pos_vals.concat(neg_vals);
        sum_of_vals = vals.reduce((x, y) => x + y, 0);
    })

    test('the sum of vals should be 2', () => {
        expect(sum(vals)).toBe(sum_of_vals);
    });

    test('should get positive values', () => {
        expect(positive(vals)).toEqual(pos_vals);
    });

    test('should get negative values', () => {
        expect(negative(vals)).toEqual(neg_vals);
    });
});

describe('testing string utilities', () => {

    test.each(["racecar", "radar", "level", "refer", "deified", "civic"])(
        'testing %s for palindrome', (word) => {
            expect(isPalindrome(word)).toBeTruthy();
        },
    );

    test.each([["arc", "car"], ["cat", "act"], ["cider", "cried"]])(
        'testing if %s and %s are anagrams ', (word1, word2) => {
            expect(isAnagram(word1, word2)).toBeTruthy();
        },
    );
});

```

使用`describe()`，我们为字符串和数学工具创建了两个独立的测试组。 例如，`beforeAll()`仅适用于数学工具。

```js
$ npx jest groups.test.js
PASS  ./groups.test.js
  testing math utilities
    √ the sum of vals should be 2 (3ms)
    √ should get positive values (1ms)
    √ should get negative values
  testing string utilities
    √ testing racecar for palindrome (1ms)
    √ testing radar for palindrome
    √ testing level for palindrome
    √ testing refer for palindrome
    √ testing deified for palindrome (1ms)
    √ testing civic for palindrome
    √ testing if arc and car are anagrams
    √ testing if cat and act are anagrams
    √ testing if cider and cried are anagrams  (1ms)

 Test Suites: 1 passed, 1 total
 Tests:       12 passed, 12 total
 Snapshots:   0 total
 Time:        1.786s
 Ran all test suites matching /groups.test.js/i.

```

我们运行测试。

## Jest 测试 Axios

在以下部分中，我们将测试使用 Axios 库的 JavaScript 代码。 首先，我们安装了`axios`和`json-server`模块。

`users.json`

```js
{
  "users": [
      {
          "id": 1,
          "first_name": "Robert",
          "last_name": "Schwartz",
          "email": "rob23@gmail.com"
      },
      {
          "id": 2,
          "first_name": "Lucy",
          "last_name": "Ballmer",
          "email": "lucyb56@gmail.com"
      },
      {
          "id": 3,
          "first_name": "Anna",
          "last_name": "Smith",
          "email": "annasmith23@gmail.com"
      },
      {
          "id": 4,
          "first_name": "Robert",
          "last_name": "Brown",
          "email": "bobbrown432@yahoo.com"
      },
      {
          "id": 5,
          "first_name": "Roger",
          "last_name": "Bacon",
          "email": "rogerbacon12@yahoo.com"
      }
  ]
}

```

这是 JSON 服务器的一些伪造数据。

`users.js`

```js
const axios = require('axios');

class Users {

     static async all() {
        let res = await axios.get('http://localhost:3000/users');
        return res;
      }
}

module.exports = Users;

```

`users.js`模块使用`axios`检索数据。 我们将测试该模块。

`users-app.js`

```js
const Users = require('./users');

async function showData() {
    let res = await Users.all();
    console.log(res.data);
}

showData();
console.log('finished')

```

`users-app.js`是使用`users.js`模块获取和输出数据的应用。

```js
$ json-server --watch users.json    

```

我们开始`json-server`。

```js
$ node users-app.js
finished
[ { id: 1,
    first_name: 'Robert',
    last_name: 'Schwartz',
    email: 'rob23@gmail.com' },
  { id: 2,
    first_name: 'Lucy',
    last_name: 'Ballmer',
    email: 'lucyb56@gmail.com' },
  { id: 3,
    first_name: 'Anna',
    last_name: 'Smith',
    email: 'annasmith23@gmail.com' },
  { id: 4,
    first_name: 'Robert',
    last_name: 'Brown',
    email: 'bobbrown432@yahoo.com' },
  { id: 5,
    first_name: 'Roger',
    last_name: 'Bacon',
    email: 'rogerbacon12@yahoo.com' } ]

```

我们运行该应用。

`users.test.js`

```js
const axios = require('axios');
const Users = require('./users');

jest.mock('axios');

test('should fetch users', () => {

    const users = [{
        "id": 1,
        "first_name": "Robert",
        "last_name": "Schwartz",
        "email": "rob23@gmail.com"
    }, {
        "id": 2,
        "first_name": "Lucy",
        "last_name": "Ballmer",
        "email": "lucyb56@gmail.com"
    }];

    const resp = { data : users };

    axios.get.mockImplementation(() => Promise.resolve(resp));

    Users.all().then(resp => expect(resp.data).toEqual(users));
});

```

该测试文件测试`users.js`模块。

```js
jest.mock('axios');

```

我们模拟模块。

```js
const users = [{
    "id": 1,
    "first_name": "Robert",
    "last_name": "Schwartz",
    "email": "rob23@gmail.com"
}, {
    "id": 2,
    "first_name": "Lucy",
    "last_name": "Ballmer",
    "email": "lucyb56@gmail.com"
}];

const resp = { data : users };

```

这是模拟模块将返回的响应。

```js
axios.get.mockImplementation(() => Promise.resolve(resp));

```

模拟实现返回带有响应的`promise`。

```js
Users.all().then(resp => expect(resp.data).toEqual(users));

```

我们测试了模拟的`Users.all()`函数。

```js
$ npx jest users.test.js
PASS  ./users.test.js
  √ should fetch users (4ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.818s
Ran all test suites matching /users.test.js/i.

```

我们进行测试。

在本教程中，我们使用 Jest 在 JavaScript 应用中进行单元测试。

您可能也对以下相关教程感兴趣： [Moment.js 教程](/javascript/momentjs/)， [Axios 教程](/javascript/axios/)， [Faker.js 教程](/javascript/faker)， [JSONServer 教程](/javascript/jsonserver/) ，[从 JavaScript 中的 URL 读取 JSON](/articles/javascriptjsonurl/) ， [Node Sass 教程](/javascript/nodesass/)， [Lodash 教程](/javascript/lodash/)。