Tests, like any other code, can be written in different ways, one of those ways is badly. In addition to some common practices and coding standards, tests have their own peculiarities that you should be aware of. In this lesson, we'll go over some of them.
One of the key rules is that tests must not affect each other. This means that any test is performed as if no other tests existed.
It's very easy to break this rule. A single test can create a file, change a variable, or write something to a database. If the rest of the tests come across these changes, they may fail when they shouldn't fail, or, conversely, pass when they shouldn't. In addition, such a situation can introduce uncertainty. Tests can occasionally fail for no apparent reason. For example, when a test runs in isolation, it works, but when it runs with others, it crashes:
let user;
test('first', () => {
user = { name: 'John' };
// ...
});
test('second', () => {
// The user that the other test created is used!
// This test depends on how the previous test works,
// and cannot work unless you run both tests in sequence
user.name = 'Peter';
});
This situation can happen particularly often in tests that actively interact with the things outside the program, such as databases or file systems. Testing side effects has its own tricky moments, we'll look at it more in the advanced testing course.
test('something', () => {
if (/* something */) {
// Execute the code in one way
// The check may be here
} else {
// Execute the code different way
// The check may be here
}
// The check may be here
});
Any branching within tests is actually several tests within one test. You should get rid of that and never write like that.
The goal of beforeEach
is to prepare the data and environment for testing, while the goal of test
is to call the code being tested and perform tests. But sometimes developers overdo it:
let result;
beforeEach(() => {
// The code being tested is called. This contradicts the idea of beforeEach.
result = sum(5, 9);
});
test('result', () => {
// Here it's only checking
expect(result).toEqual(14);
});
In this example, the code being tested is called in beforeEach
. This approach makes it difficult to analyze the tests because it turns everything upside down.
Programmers, influenced by advices from the Internet, tend to spread the code as much as possible across files, modules, and functions. The same is observed in testing. Instead of one test, which contains all the necessary checks, the programmer creates 5 tests, each of which contains exactly one check:
test('create user', () => {
const user = { name: 'Mark', age: 28 };
// Here's the code to add the user to the database
expect(user.age).toEqual(28);
});
test('create user 2', () => {
const user = { name: 'Mark', age: 28 };
// Here's the code to add the user to the database
expect(user.name).toEqual('Mark');
});
More often than not, this separation results in more code, and refactoring, later on, will be more complex because of the amount of code.
Jest allows you to group tests into describe
blocks:
describe('User', () => {
test('should be valid', () => { /* ... */ });
});
They help you structure complex tests and assign each describe
block to its own beforeEach
. Although this feature can be useful, it's very easy to start using it to your detriment:
describe('', () => {
describe('...', () => {
describe('...', () => {
test('should be valid', () => { /* ... */ })
});
});
});
An entrenched test hierarchy makes them difficult to analyze and keeps them bonded to one another. This complicates adding new checks. It makes it unclear what each test refers to. This is the problem with any hierarchies that view the system from only one point of view.
This is a rather crucial topic, which you can use to test how good a programmer is at writing tests. Although some kinds of tests are quite complex and require extra time, the ordinary tests that are written along the code help speed up development. And there are five reasons for that:
The Hexlet support team or other students will answer you.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.
Programming courses for beginners and experienced developers. Start training for free
Our graduates work in companies:
Sign up or sign in
Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.