Register to get access to free programming courses with interactive exercises

Fixtures JS: Advanced Testing

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.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

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.