Jest 教程展示了如何使用 Jest 框架在 JavaScript 应用程序中执行单元测试。
是
Jest 专注于简单性的 JavaScript 休息框架。 Jest 是由 Facebook 工程师为其 React 项目创建的。
单元测试 是一种软件测试,其中测试软件的各个单元(组件)。单元测试的目的是验证软件的每个单元是否按设计执行。单元是任何软件中最小的可测试部分。
模拟 是一种将代码部分替换为模拟真实代码的虚拟实现的技术。模拟有助于实现测试的隔离。模拟主要用于单元测试。
在我们的测试中,我们检查值是否满足特定条件。 expect
函数为我们提供了许多匹配器,让我们验证不同的事物,例如toBe
、toBeFalsy
或toEqual
。
在本文中,我们在 Node 应用程序中使用 Jest。
设置 Jest
首先,我们安装 Jest。
$ npm init -y
我们启动一个新的 Node 应用程序。
$ npm i --dev jest
我们使用nmp i --dev jest
安装 Jest 模块。
$ npm i -g jsonserver $ npm i axios
我们也将使用jsonserver
和axios
。
package.json
测试脚本运行jest
。
{ "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" } }
默认情况下,jest 只提供基本的输出。要获得有关测试运行的更多信息,我们使用 --verbose
标志。
Jest 运行测试
测试使用npm test
命令运行。测试文件的名称中必须包含test
术语。
$ 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
命令。
scripts:{ "test": "jest --verbose ./test-directory" }
我们可以将 Jest 配置为在指定的测试目录中运行测试。
用 Jest 测试算术函数
以下是使用 Jest 演示单元测试的经典学术示例。
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 };
我们在一个模块中有四个基本算术函数。
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
中,我们测试模块。文件名包含测试术语。然后由 jest 采摘。
test('2 + 3 = 5', () => { expect(add(2, 3)).toBe(5); });
我们用add
函数测试test
方法。第一个参数是测试的名称,第二个参数是要运行的函数。我们正在测试 add
函数是否为示例数据返回正确答案。
$ 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 跳过测试
测试可能需要相当长的时间才能完成。如果需要,我们可以跳过一些测试。
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
前缀跳过测试。在我们的例子中,前两个测试被跳过。
$ 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
全局函数。
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); }, );
在这些测试中,我们使用不同的输入数据多次运行每个算术函数。
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
选项显示的输出。
$ 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.
开玩笑
beforeAll
函数是测试设置的一部分。它在此文件中的任何测试运行之前运行一个函数。如果该函数返回一个 promise 或者是一个生成器,Jest 会在运行测试之前等待该 promise 解决。
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
。
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
函数在测试运行前初始化测试数据。
test('should get positive values', () => { expect(positive(vals)).toEqual(pos_vals); });
为了测试positive
函数,我们使用toEqual
matcher。我们测试该函数返回的正值数组等于预定义的测试值数组。
Jest 分组测试
在 Jest 中,测试用describe
分组到单元中。它创建了一个块,将几个相关的测试组合在一起。
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
。
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
模块。
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
仅适用于数学实用程序。
$ 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
modules。
{ "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 服务器的一些假数据。
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
检索数据。我们将测试此模块。
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
模块获取和输出数据的应用。
$ json-server --watch users.json
我们开始json-server
。
$ 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' } ]
我们运行应用程序。
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
模块。
jest.mock('axios');
我们模拟模块。
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.all
函数。
$ 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 应用程序中进行单元测试。
列出所有 JavaScript 教程。