With the widespread use of JavaScript in front-end and back-end development, testing has become a critical part of ensuring code quality.
Why you need unit tests
In our development process, it is often necessary to define a number ofalgorithmic functionFor example, converting the data returned by the interface into the format required by the UI component. In order to verify the robustness of these algorithms, some developers may manually define a few input samples for initial verification, and once the verification passes, they will not be further investigated.
However, such an approach may bring somePotential problems. First, the boundary value case is often easily overlooked, leading to incomplete calibration and increasing the risk of system failure. Second, with the change and evolution of requirements, the algorithmic functions may need to be optimized and expanded. If the pre-checking work is not thorough enough and does not understand the specific scenarios covered by the existing functions, it may lead to the introduction of new problems in the subsequent modifications.
unit testThe above problem can be solved effectively. When defining the algorithmic function, synchronize the creation of unit test files, and the possible scenarios are listed one by one. If the unit test fails, the project will directly report errors when compiling, so that the problem can be identified and targeted in a timely manner. In addition, when new students join and need to extend the function, they not only need to add new test cases on the basis of the original unit test, but also to ensure the correctness of the new function, while guaranteeing the normal operation of the original function.
Customized Test Logic
Before we start using tools for unit testing, we can customize a tool function for testing.
For example, we have aadd
function, expecting it to correctly compute the sum of two numbers, and verifying that the result is as expected. For example, we want to verify that2 + 3
Is the result of the5
You can use theexpect(add(2, 3)).toBe(5)
code like this to do so. To do this, we can define our ownexpect
function so that it has something like Jest'sexpect
Functions of the function
function add(a, b) { return a + b; }
function expect(result) {
return {
toBe(value) {
if (result === value) {
("Validation succeeded"); } else {
} else {
throw new Error(`Execution error: ${result} ! == ${value}`); }
}
}, }
}; }
}
// Example call
try {
expect(add(2, 3)).toBe(5); // Output: "Validation successful"
expect(add(2, 3)).toBe(6); // throws error
} catch (err) {
(); // Output: "Execution error: 5 ! == 6"
}
To make our tests more descriptive and readable, we can further enhance our test logic. For example, we can add atest
function that describes the purpose of the test and provides a more detailed error message if the test fails.
function test(description, fn) {
fn(); fn(); fn(); fn(); fn()
fn(); (`Test passed: ${description}`); fn()
(`Test Passed: ${description}`); } catch (err) {
} catch (err) {
(`Test failed: ${description} - ${}`); }
}
}
// Example call
test("Verify that 2 + 3 is equal to 5", () => {
expect(add(2, 3)).toBe(5); }); }
}); test("Verify that 2 + 3 equals 5", () => { expect(add(2, 3)).toBe(5); }).
test("Verify that 2 + 3 is equal to 6", () => {
expect(add(2, 3)).toBe(6);
}).
In this way, we modeled a simple test case where thetest
respond in singingexpect
function is similar to the one in Jest. However, our customized version is relatively rudimentary, lacking theJest
Rich functionality provided.
Jest
Through the above examples, we can understand the basic ideas and methods of writing tests. However, in actual development, we need a more powerful and easy-to-use testing tool.Jest
It is such a tool that not only provides a wealth ofmatchmaker
(e.g., toBe, toEqual, etc.), and also support for theasynchronous testing
、Mock Functions
、Snapshot test
and other features.
pull intoJest
dependency, we can directly use its built-intest
respond in singingexpect
function, thus greatly improving the efficiency and accuracy of the test.Jest
The power of this is that it can help us to comprehensively cover various test scenarios and provide detailed error reports so that we can quickly locate and solve problems.
initialization
First of all, we pass thenpm install jest -D
mountingJest
dependency, and then execute thenpx jest --init
. At this point, the command-line tool presents you with a series of interactive questions and answers asking you whether you want to add a script command called test to Jest, whether you want to use TypeScript as a configuration file, the environment in which your test cases will be executed, whether you want a code coverage test report, the compiler of the platform on which the test report will be generated, and whether you want to reset the state of the Mock function before each test case execution.
After completing all the questions and answers, Jest will modify the file and generates the
Configuration files. These configuration items will be relied upon when executing test cases.
We create a file and put the previous test code into it
function add(a, b) {
return a + b;
}
test("beta (software) add function (math.)", () => {
expect(add(2, 3)).toBe(5);
});
pass (a bill or inspection etc)npm run test
Executing the Jest run command allows you to view detailed test information at the command line tool, including the status of which test case for which file, as well as a simple test coverage report.
In practical usage scenarios, theadd
Functions are usually defined in the project file, and are accessed through the ES Modularity
(export and import). By default, Jest does not support ES modular syntax, so we need to configure it with Babel.
First, install Babel and its core libraries and presets by running the following commands
npm install @babel/core @babel/preset-env --save-dev
Then, create thefile and define the configuration
= {
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current",
},
},
],
],
};
Next, place theadd
function is moved to the file with the
export
derive
//
export function add(a, b) {
return a + b;
}
Finally, use import in the file to import the
//
import { add } from './math';
test("beta (software) add function (math.)", () => {
expect(add(2, 3)).toBe(5);
});
With these steps, you have completed the environment initialization for executing ES modular code with Jest.
matchmaker
One of the most commonly used features in Jest is the matcher. We touched on this earlier when we were doing testing with thetoBe
This is a matcher, which is used to determine whether the values are equal or not. In addition to this, there are many other types of matchers.
equal in value
There are two types of matchers for determining the equality of values:toBe
cap (a poem)toEqual
. For basic data types (such as strings, numbers, and booleans), both are used with the same effect. But for reference types (such as objects and arrays), thetoBe
Returns only if both references point to the same memory addresstrue
。
const user = { name: "alice" };
const info = { name: "alice" };
test("toEqual", () => {
expect(info).toEqual(user); // passes, both have the same structure
}); test("toBe", () => { expect(info).
test("toBe", () => {
expect(info).toBe(user); // fails, they have different references
}).
Is there a value
remaintoBeNull
、toBeUndefined
respond in singingtoBeDefined
matcher to determine if the value is null, undefined, or defined, respectively.
test("toBeNull", () => {
expect(null).toBeNull();
expect(0).toBeNull(); // fail
expect("hello").toBeNull(); // fail
expect(undefined).toBeBull(); // fail
});
test("toBeUnDefined", () => {
expect(null).toBeUndefined(); // fail
expect(0).toBeUndefined(); // fail
expect("hello").toBeUndefined(); // fail
expect(undefined).toBeUndefined();
});
test("toBeDefined", () => {
expect(null).toBeDefined();
expect(0).toBeDefined();
expect("hello").toBeDefined();
expect(undefined).toBeDefined(); // fail
});
whether it is true or not
toBeTruthy
is used to determine whether a value is true or not.toBeFalsy
is used to determine if the value is false.not
Used for taking the opposite.
test("toBeTruthy", () => {
expect(null).toBeTruthy(); // fails
expect(0).toBeTruthy(); // not passed
expect(1).toBeTruthy(); // fail
expect("").toBeTruthy(); // fail
expect("hello").toBeTruthy(); // fail
expect(undefined).toBeTruthy(); // fails
});
test("toBeFalsy", () => {
expect(null).toBeFalsy();
expect(0).toBeFalsy();
expect(1).toBeFalsy(); // not passed
expect("").toBeFalsy();
expect("hello").toBeFalsy(); // fails
expect(undefined).toBeFalsy(); // Not passed.
}).
test("not", () => {
expect(null). ();
expect("hello"). (); // not passed
});
numerical comparison
toBeGreaterThan
is used to determine whether it is greater than a certain value.toBeLessThan
is used to determine whether it is less than a certain value.toBeGreaterThanOrEqual
is used to determine whether it is greater than or equal to a certain value.toBeCloseTo
Used to determine if it is close to a certain value (difference < 0.005).
test("toBeGreaterThan", () => {
expect(9).toBeGreaterThan(5);
expect(5).toBeGreaterThan(5); // fail
expect(1).toBeGreaterThan(5); // fail
});
test("toBeLessThan", () => {
expect(9).toBeLessThan(5); // fail
expect(5).toBeLessThan(5); // fail
expect(1).toBeLessThan(5);
});
test("toBeGreaterThanOrEqual", () => {
expect(9).toBeGreaterThanOrEqual(5);
expect(5).toBeGreaterThanOrEqual(5);
expect(1).toBeGreaterThanOrEqual(5); // fail
});
test("toBeCloseTo", () => {
expect(0.1 + 0.2).toBeCloseTo(0.3);
expect(1 + 2).toBeCloseTo(3);
expect(0.1 + 0.2).toBeCloseTo(0.4); // fail
});
string related
toMatch
Used to determine whether the string contains the specified substring, partially contains can be.
test("toMatch", () => {
expect("alice").toMatch("alice"); // passed
expect("alice").toMatch("alice"); // passed
expect("alice").toMatch("al"); // passed
}).
Array Related
toContain
is used to determine whether an array contains the specified element, similar to the JavaScriptincludes
Methods.
test("toContain", () => {
expect(['banana', 'apple', 'orange']).toContain("apple");
expect(['banana', 'apple', 'orange']).toContain("app"); // fail
});
error-related
toThrow
Used to determine whether a function throws an exception, and can specify the specifics of the exception thrown.
test("toThrow", () => {
const throwNewErrorFunc = () => {
throw new TypeError("this is a new error");
};
expect(throwNewErrorFunc).toThrow();
expect(throwNewErrorFunc).toThrow("new error");
expect(throwNewErrorFunc).toThrow("TypeError"); // fail
});
These are the common matchers for each type.
command-line tool
exist Medium Configuration
script
command, which enables the.
The file automatically executes test cases in real time as they are modified.
"scripts": {
"jest": "jest --watchAll"
},
From the command line, you will see the results of the current test case execution in real time. Meanwhile, Jest also provides some shortcut configurations by pressing thew
key to see exactly what commands are available.
There are several main types:
f Mode
Among all test cases, only the last failed test case is executed. Even if the content of other test cases is modified, they will not be executed.
o Mode
Execute only modified test cases. This feature needs to be used in conjunction with theGit
based on the changes made to the Git repository relative to the last time it was used. This pattern can also be achieved by configuring the script command, which is:
"script": {
"test": "jest --watch"
}
p-mode
When using the --watchAll
When you modify the code of a file, all test cases are executed. Enter thep
After the mode, you can enter the file namematchersFile
If you modify any file at this point, it will only look for the file that contains thematchersFile
file and execute it.
t-mode
Enter a test case name that matches the first argument of the test function. If the match is successful, the test case is executed.
q-mode
Exit real-time code detection.
With different commands, you can test test cases in a more targeted way.
hook function
In Jest, the describe function is used to put together a set of related test cases (tests) to form a descriptive test block. It takes two arguments: the first is a string that describes the subject of the test block; the second is a function that contains a set of test cases.
Even if the describe function is not explicitly defined, each test file is wrapped with a layer of describe by default at the outermost level.
Within each of the blocks that describe consists of, there are a number of hook functions that are used throughout the test case. These hooks are mainly used to prepare the test case before execution or to clean it up afterwards.
Common Hook Functions
- The beforeAll function is executed once before the start of a describe block.
- The afterAll function is executed once at the end of a describe block.
- The beforeEach function is executed before each test case
- afterEach is executed after each test case.
sample code (computing)
The following sample code shows how to use these hook functions:
describe("Testing for values", () => {
beforeAll(() => {
("beforeAll");
});
afterAll(() => {
("afterAll");
}).
beforeEach(() => {
("beforeEach");
}).
describe("toBeNull", () => {
beforeAll(() => {
("toBeNull beforeAll");
});
afterAll(() => {
("toBeNull afterAll");
});
beforeEach(() => {
("toBeNull beforeEach");
});
test("toBeNull", () => {
expect(null).toBeNull(); }); test("toBeNull", () => {
});
});
});
output sequence
When the above test case is run, the sequence of output is as follows:
beforeAll
toBeNull beforeAll
beforeEach
toBeNull beforeEach
toBeNull afterAll
afterAll
By using these hook functions, you can better manage the lifecycle of your test cases, ensuring that each test starts with a clean state and cleaning up any side effects generated at the end of the test.
In this test guide, we introduce theBackground of Jest, how to initialize a project, common matcher syntax, hook functionsThe next post will continue to delve into Jest's advanced features, including The next article will continue to delve into Jest's advanced features, includingMock functions, handling of asynchronous requests, mocking of Mock requests, mocking of classes and timers, use of snapshots. With these techniques, we will be able to write and maintain test cases more efficiently, especially when dealing with complex asynchronous logic and external dependencies.