JS: Automated testing
Theory: Data preparation
Most tests on a given feature are very similar to each other, especially in terms of initial data preparation. In the last lesson, each test started with the line makeStack(). We're not duplicating things just yet, but we're making steps towards it. Generally, actual tests are more complicated and involve a ton of preparatory work.
Imagine we're working on the Lodash library and want to test its functions for processing collections:
- find
- filter
- includes
- and others (there are about 20 of them in total)
These functions require a pre-prepared collection to work. It is easiest to come up with one that's suitable for testing most or even all the functions:
Now imagine that there are dozens of these tests (in fact, there are hundreds). The code will wander from place to place, generating more and more copies and pastes of itself.
The easiest way to avoid this is to move where the collection is defined to the module level, outside of the test functions:
This simple solution removes unnecessary duplication. Note, however, that it only works within a single module. Collections like this still have to be defined in each test module. And in our case, this is actually good for us.
The point is that any kind of overgeneralization that leads to a complete elimination of redundancy introduces implicit dependencies into the code. Changing this collection will almost certainly break most of the tests that are tied to its structure, as well as to the number of elements and their values:
The test above would break if we added another even number to our collection. And the collection will almost certainly have to be expanded when new tests are added (for this or other functions).
The main conclusion from this is we need to get rid of and avoid duplications. But it's important not to cross the line and allow generalization to start to hinder rather than help.
But it's not always possible to bring the constants to module level. This applies primarily to dynamic data. Imagine some code like this:
The catch here is that the module is loaded into memory exactly once. This means that all the code defined at the module level (including constants) is executed exactly once. For example, the constant now will be defined before all the tests are run, and only after will Jest start to carry out tests. And with each subsequent test, the lag between the value of the constant now and the current real value of now will be further and further away.
Why might that be a problem? A code that works with the concept of "now" can count on the fact that "now" is kind of a snapshot of a given moment in time. But in the example above, now begins to lag behind the real now, and the more tests and the more complex they are, the greater the lag.
It is important not to forget that the test function does not run the test. It adds it inside Jest, and Jest decides when to perform this test. Therefore, an indefinite amount of time passes between loading the module and running the tests.
To solve this problem, test frameworks provide hooks — special functions that run before or after tests. Below is an example of how to create a date before each test:
beforeEach(callback) takes the function performing an initialization. It doesn't necessarily create variables. Perhaps initialization is about preparing the file system, such as by creating files.
But if it has to create data and make them available in tests, you have to use variables defined at the module level. Since everything that's defined within a function (the callback in our case) stays inside that function.
Even if we need to execute the code once before all the tests, it still needs to be executed inside the hook beforeAll(callback). This hook is run exactly once before all the tests are located in a given module.
Why does it matter? To answer this question, we need to know a little more about the asynchronous nature of JavaScript. This issue is dealt with later in the course, but for now, we can limit ourselves to this: Jest must control the processes and side effects in tests. Everything that's called at the module level is executed outside of Jest. This means that Jest has no way to keep track of what is happening and at what point you can run tests.

