Imagine a function that takes some HTML as input, extracts all the links from it, and returns them as an array:
// To test such a function, we recommend taking HTML code that resembles real-life code as much as possible.
// Although it doesn't guarantee that the function will work, it at least gives the document variety in its structure.
// This HTML is taken from our project cv.hexlet.io
const html = `
<div class="card mb-3"><div class="card-body"><div class="d-flex flex-column flex-sm-row">
<div class="d-flex flex-column mr-4"><div class="text-muted text-center mb-3">
<div class="h2 mb-0 font-weight-lighter">1</div><div class="small">Reply</div></div>
<div class="text-muted text-center mb-3"><div class="h2 mb-0 font-weight-lighter">7</div>
<div class="small">Viewings</div></div></div><div><h5 class="card-title">
<a href="/resumes/1">Backend Software Engineer</a></h5><div class="card-text">
<p>Self-taught programmer, who has chosen the path of continuous self-improvement.
I appreciate beautiful and concise code, I like functional programming
(great trinity <code>map</code>, <code>filter</code>, <code>reduce</code>).</p>
<p>I use JS, Ruby, PHP, Python, Elixir, Clojure to varying degrees of skill.</p>
<p>I admire the LISP language family and am writing my own interpreter LISP on Elixir.
At the moment, I am delving into Unix OS in order to improve my DevOps skills in the future.</p>
</div><div class="text-right small"><span class="mr-3 text-muted">12 days</span>
<a href="/users/6">John Smith in</a></div></div></div></div></div>
`;
const links = extractLinks(html);
console.log(links);
// => ['/resumes/1', '/users/6']
The piece of HTML at the beginning of the test looks scary. It's large and consists of a massive load of tags. Of course, you can try to format it, but you'll have to do it all manually. For any editor, it's just a line in JavaScript. But it's not just about formatting, this way of working with large chunks of data has other disadvantages:
- It's very easy to make a mistake when preparing updates, and they're difficult to detect visually. The editor can't help here either.
- The more such data in tests, the harder it is to read and separate the logic from the data itself.
It would be much more convenient if HTML was stored as normal HTML in its own file. It isn't hard to do. In this case, the test will look like this:
import fs from 'fs';
// Jest supports functions with async/await
test('extractLinks', async () => {
// the HTML is in the file withLinks.html in the __fixtures__ directory
// When reading text files, a blank line may be added at the end.
// It can be removed with the `trim` method if needed
// __dirname – is the directory in which this file with tests is located
const html = await fs.readFile(`${__dirname}/../__fixtures__/withLinks.html`, 'utf-8');
// Now HTML is easy to work with and doesn't clutter up the tests.
const links = extractLinks(html);
expect(links).toEqual(['/resumes/1', '/users/6']);
});
The data needed during test runs are called fixtures. It isn't necessarily text data. Fixtures can be images, JSON- and XML-files, database records, and more. Sometimes code can also be part of the fixture, but this is quite rare. Such fixtures are needed when testing different code analyzers, such as ESLint or Babel.
Usually, fixtures are stored in separate files in their own directory. In Jest, it's recommended to create a __fixtures__ directory in the root of the project for this purpose. They're then read and used as and when.
An example:
tree __fixtures__
├── after.ini
├── after.json
├── after.yml
├── before.ini
├── before.json
├── before.yml
└── result.txt
When there's more than one fixture, many similar calls that read files will start to appear in the test code:
// Somewhere in the tests or hooks
const html = await fs.readFile(`${__dirname}/../__fixtures__/withLinks.html`, 'utf-8');
const json = await fs.readFile(`${__dirname}/../__fixtures__/somethingElse.json`, 'utf-8');
In this case, it is better to bring the construction of the path in a separate function, and at the same time use the correct way of gluing paths:
import path from 'path';
const getFixturePath = (filename) => path.join(__dirname, '..', '__fixtures__', filename);
const readFile = (filename) => fs.readFile(getFixturePath(filename), 'utf-8');
// The files themselves must be read either inside tests or inside hooks, such as `beforeAll` or `beforeEach`.
// Don't do this at the module level, outside of functions.
const html = await readFile('withLinks.html');
const json = await readFile('somethingElse.json');
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
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.