标题: http://zetcode.com/javascript/jest/

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

Jest

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

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

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

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

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

安装 Jest

首先,我们安装 Jest。

  1. $ node -v
  2. v11.5.0

我们使用 Node 版本 11.5.0。

  1. $ npm init -y

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

  1. $ npm i --dev jest

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

  1. $ npm i -g jsonserver
  2. $ npm i axios

我们也将使用jsonserveraxios

package.json

测试脚本运行jest

package.json

  1. {
  2. "name": "jest-test",
  3. "version": "1.0.0",
  4. "description": "",
  5. "main": "main.js",
  6. "scripts": {
  7. "test": "jest --verbose"
  8. },
  9. "keywords": [],
  10. "author": "Jan Bodnar",
  11. "license": "ISC",
  12. "devDependencies": {
  13. "jest": "^24.0.0"
  14. },
  15. "dependencies": {
  16. "axios": "^0.18.0"
  17. }
  18. }

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

Jest 运行测试

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

  1. $ npm test
  2. > jest-test@1.0.0 test C:\Users\Jano\Documents\js\jest-test
  3. > jest
  4. PASS ./math-utils.test.js
  5. PASS ./arith.test.js
  6. PASS ./arith-params.test.js
  7. PASS ./arith-skip.test.js
  8. PASS ./string-utils.test.js
  9. PASS ./arith-mock.test.js
  10. PASS ./users.test.js
  11. Test Suites: 7 passed, 7 total
  12. Tests: 2 skipped, 35 passed, 37 total
  13. Snapshots: 0 total
  14. Time: 5.19s
  15. Ran all test suites.

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

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

  1. scripts:{
  2. "test": "jest --verbose ./test-directory"
  3. }

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

用 Jest 测试算术函数

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

arith.js

  1. const add = (a, b) => a + b;
  2. const mul = (a, b) => a * b;
  3. const sub = (a, b) => a - b;
  4. const div = (a, b) => a / b;
  5. module.exports = { add, mul, sub, div };

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

arith.test.js

  1. const { add, mul, sub, div } = require('./arith');
  2. test('2 + 3 = 5', () => {
  3. expect(add(2, 3)).toBe(5);
  4. });
  5. test('3 * 4 = 12', () => {
  6. expect(mul(3, 4)).toBe(12);
  7. });
  8. test('5 - 6 = -1', () => {
  9. expect(sub(5, 6)).toBe(-1);
  10. });
  11. test('8 / 4 = 2', () => {
  12. expect(div(8, 4)).toBe(2);
  13. });

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

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

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

  1. $ npx jest arith.test.js
  2. PASS ./arith.test.js
  3. 2 + 3 = 5 (3ms)
  4. 3 * 4 = 12 (6ms)
  5. 5 - 6 = -1
  6. 8 / 4 = 2 (1ms)
  7. Test Suites: 1 passed, 1 total
  8. Tests: 4 passed, 4 total
  9. Snapshots: 0 total
  10. Time: 10.981s
  11. Ran all test suites matching /arith.test.js/i.

这是输出。

Jest 跳过测试

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

arith-skip.test.js

  1. const { add, mul, sub, div } = require('./arith');
  2. xtest('2 + 3 = 5', () => {
  3. expect(add(2, 3)).toBe(5);
  4. });
  5. test.skip('3 * 4 = 12', () => {
  6. expect(mul(3, 4)).toBe(12);
  7. });
  8. test('5 - 6 = -1', () => {
  9. expect(sub(5, 6)).toBe(-1);
  10. });
  11. test('8 / 4 = 2', () => {
  12. expect(div(8, 4)).toBe(2);
  13. });

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

  1. $ npx jest arith-skip.test.js
  2. PASS ./arith-skip.test.js
  3. 5 - 6 = -1 (2ms)
  4. 8 / 4 = 2 (1ms)
  5. skipped 2 tests
  6. Test Suites: 1 passed, 1 total
  7. Tests: 2 skipped, 2 passed, 4 total
  8. Snapshots: 0 total
  9. Time: 2.323s
  10. Ran all test suites matching /arith-skip.test.js/i.

跳过了两个测试。

Jest 参数化测试

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

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

arith-param.test.js

  1. const { add, mul, sub, div } = require('./arith')
  2. test.each([[1, 1, 2], [-1, 2, 1], [2, 1, 3]])(
  3. '%i + %i equals %i', (a, b, expected) => {
  4. expect(add(a, b)).toBe(expected);
  5. },
  6. );
  7. test.each([[1, 1, 0], [-1, 2, -3], [2, 2, 0]])(
  8. '%i - %i equals %i', (a, b, expected) => {
  9. expect(sub(a, b)).toBe(expected);
  10. },
  11. );
  12. test.each([[1, 1, 1], [-1, 2, -2], [2, 2, 4]])(
  13. '%i * %i equals %i', (a, b, expected) => {
  14. expect(mul(a, b)).toBe(expected);
  15. },
  16. );
  17. test.each([[1, 1, 1], [-1, 2, -0.5], [2, 2, 1]])(
  18. '%i / %i equals %i', (a, b, expected) => {
  19. expect(div(a, b)).toBe(expected);
  20. },
  21. );

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

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

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

  1. $ npx jest arith-params.test.js
  2. PASS ./arith-params.test.js
  3. 1 + 1 equals 2 (3ms)
  4. -1 + 2 equals 1 (1ms)
  5. 2 + 1 equals 3
  6. 1 - 1 equals 0
  7. -1 - 2 equals -3
  8. 2 - 2 equals 0
  9. 1 * 1 equals 1 (1ms)
  10. -1 * 2 equals -2
  11. 2 * 2 equals 4
  12. 1 / 1 equals 1 (1ms)
  13. -1 / 2 equals 0
  14. 2 / 2 equals 1
  15. Test Suites: 1 passed, 1 total
  16. Tests: 12 passed, 12 total
  17. Snapshots: 0 total
  18. Time: 1.759s
  19. Ran all test suites matching /arith-params.test.js/i.

这是输出。

Jest

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

math-utils.js

  1. const sum = (vals) => {
  2. let sum = 0;
  3. vals.forEach((val) => {
  4. sum += val;
  5. });
  6. return sum;
  7. }
  8. const positive = (vals) => {
  9. return vals.filter((x) => { return x > 0; });
  10. }
  11. const negative = (vals) => {
  12. return vals.filter((x) => { return x < 0; });
  13. }
  14. module.exports = { sum, positive, negative };

我们有一个math-utils模块,其中包含三个函数:sum()positive()negative()

math-utils.test.js

  1. const { sum, positive, negative } = require('./math-utils');
  2. let vals;
  3. let sum_of_vals;
  4. let pos_vals;
  5. let neg_vals;
  6. beforeAll(() => {
  7. pos_vals = [2, 1, 3];
  8. neg_vals = [-2, -1, -1];
  9. vals = pos_vals.concat(neg_vals);
  10. sum_of_vals = vals.reduce((x, y) => x + y, 0);
  11. })
  12. test('the sum of vals should be 2', () => {
  13. expect(sum(vals)).toBe(sum_of_vals);
  14. });
  15. test('should get positive values', () => {
  16. expect(positive(vals)).toEqual(pos_vals);
  17. });
  18. test('should get negative values', () => {
  19. expect(negative(vals)).toEqual(neg_vals);
  20. });

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

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

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

Jest 分组测试

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

string-utils.js

  1. const isPalindrome = (string) => string == string.split('').reverse().join('');
  2. const isAnagram = (w1, w2) => {
  3. const regularize = (word) => {
  4. return word.toLowerCase().split('').sort().join('').trim();
  5. }
  6. return regularize(w1) === regularize(w2);
  7. }
  8. module.exports = {isPalindrome, isAnagram};

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

math-utils.js

  1. const sum = (vals) => {
  2. let sum = 0;
  3. vals.forEach((val) => {
  4. sum += val;
  5. });
  6. return sum;
  7. }
  8. const positive = (vals) => {
  9. return vals.filter((x) => { return x > 0; });
  10. }
  11. const negative = (vals) => {
  12. return vals.filter((x) => { return x < 0; });
  13. }
  14. module.exports = { sum, positive, negative };

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

groups.test.js

  1. const { sum, positive, negative } = require('./math-utils');
  2. const { isPalindrome, isAnagram } = require('./string-utils');
  3. describe('testing math utilities', () => {
  4. let vals;
  5. let sum_of_vals;
  6. let pos_vals;
  7. let neg_vals;
  8. beforeAll(() => {
  9. pos_vals = [2, 1, 3];
  10. neg_vals = [-2, -1, -1];
  11. vals = pos_vals.concat(neg_vals);
  12. sum_of_vals = vals.reduce((x, y) => x + y, 0);
  13. })
  14. test('the sum of vals should be 2', () => {
  15. expect(sum(vals)).toBe(sum_of_vals);
  16. });
  17. test('should get positive values', () => {
  18. expect(positive(vals)).toEqual(pos_vals);
  19. });
  20. test('should get negative values', () => {
  21. expect(negative(vals)).toEqual(neg_vals);
  22. });
  23. });
  24. describe('testing string utilities', () => {
  25. test.each(["racecar", "radar", "level", "refer", "deified", "civic"])(
  26. 'testing %s for palindrome', (word) => {
  27. expect(isPalindrome(word)).toBeTruthy();
  28. },
  29. );
  30. test.each([["arc", "car"], ["cat", "act"], ["cider", "cried"]])(
  31. 'testing if %s and %s are anagrams ', (word1, word2) => {
  32. expect(isAnagram(word1, word2)).toBeTruthy();
  33. },
  34. );
  35. });

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

  1. $ npx jest groups.test.js
  2. PASS ./groups.test.js
  3. testing math utilities
  4. the sum of vals should be 2 (3ms)
  5. should get positive values (1ms)
  6. should get negative values
  7. testing string utilities
  8. testing racecar for palindrome (1ms)
  9. testing radar for palindrome
  10. testing level for palindrome
  11. testing refer for palindrome
  12. testing deified for palindrome (1ms)
  13. testing civic for palindrome
  14. testing if arc and car are anagrams
  15. testing if cat and act are anagrams
  16. testing if cider and cried are anagrams (1ms)
  17. Test Suites: 1 passed, 1 total
  18. Tests: 12 passed, 12 total
  19. Snapshots: 0 total
  20. Time: 1.786s
  21. Ran all test suites matching /groups.test.js/i.

我们运行测试。

Jest 测试 Axios

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

users.json

  1. {
  2. "users": [
  3. {
  4. "id": 1,
  5. "first_name": "Robert",
  6. "last_name": "Schwartz",
  7. "email": "rob23@gmail.com"
  8. },
  9. {
  10. "id": 2,
  11. "first_name": "Lucy",
  12. "last_name": "Ballmer",
  13. "email": "lucyb56@gmail.com"
  14. },
  15. {
  16. "id": 3,
  17. "first_name": "Anna",
  18. "last_name": "Smith",
  19. "email": "annasmith23@gmail.com"
  20. },
  21. {
  22. "id": 4,
  23. "first_name": "Robert",
  24. "last_name": "Brown",
  25. "email": "bobbrown432@yahoo.com"
  26. },
  27. {
  28. "id": 5,
  29. "first_name": "Roger",
  30. "last_name": "Bacon",
  31. "email": "rogerbacon12@yahoo.com"
  32. }
  33. ]
  34. }

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

users.js

  1. const axios = require('axios');
  2. class Users {
  3. static async all() {
  4. let res = await axios.get('http://localhost:3000/users');
  5. return res;
  6. }
  7. }
  8. module.exports = Users;

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

users-app.js

  1. const Users = require('./users');
  2. async function showData() {
  3. let res = await Users.all();
  4. console.log(res.data);
  5. }
  6. showData();
  7. console.log('finished')

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

  1. $ json-server --watch users.json

我们开始json-server

  1. $ node users-app.js
  2. finished
  3. [ { id: 1,
  4. first_name: 'Robert',
  5. last_name: 'Schwartz',
  6. email: 'rob23@gmail.com' },
  7. { id: 2,
  8. first_name: 'Lucy',
  9. last_name: 'Ballmer',
  10. email: 'lucyb56@gmail.com' },
  11. { id: 3,
  12. first_name: 'Anna',
  13. last_name: 'Smith',
  14. email: 'annasmith23@gmail.com' },
  15. { id: 4,
  16. first_name: 'Robert',
  17. last_name: 'Brown',
  18. email: 'bobbrown432@yahoo.com' },
  19. { id: 5,
  20. first_name: 'Roger',
  21. last_name: 'Bacon',
  22. email: 'rogerbacon12@yahoo.com' } ]

我们运行该应用。

users.test.js

  1. const axios = require('axios');
  2. const Users = require('./users');
  3. jest.mock('axios');
  4. test('should fetch users', () => {
  5. const users = [{
  6. "id": 1,
  7. "first_name": "Robert",
  8. "last_name": "Schwartz",
  9. "email": "rob23@gmail.com"
  10. }, {
  11. "id": 2,
  12. "first_name": "Lucy",
  13. "last_name": "Ballmer",
  14. "email": "lucyb56@gmail.com"
  15. }];
  16. const resp = { data : users };
  17. axios.get.mockImplementation(() => Promise.resolve(resp));
  18. Users.all().then(resp => expect(resp.data).toEqual(users));
  19. });

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

  1. jest.mock('axios');

我们模拟模块。

  1. const users = [{
  2. "id": 1,
  3. "first_name": "Robert",
  4. "last_name": "Schwartz",
  5. "email": "rob23@gmail.com"
  6. }, {
  7. "id": 2,
  8. "first_name": "Lucy",
  9. "last_name": "Ballmer",
  10. "email": "lucyb56@gmail.com"
  11. }];
  12. const resp = { data : users };

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

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

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

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

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

  1. $ npx jest users.test.js
  2. PASS ./users.test.js
  3. should fetch users (4ms)
  4. Test Suites: 1 passed, 1 total
  5. Tests: 1 passed, 1 total
  6. Snapshots: 0 total
  7. Time: 1.818s
  8. Ran all test suites matching /users.test.js/i.

我们进行测试。

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

您可能也对以下相关教程感兴趣: Moment.js 教程Axios 教程Faker.js 教程JSONServer 教程从 JavaScript 中的 URL 读取 JSONNode Sass 教程Lodash 教程