Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Testing with supertest

An automated test is code that opens pages and checks the responses by itself. You run it after every change and know within seconds whether something broke. Node.js has a built-in node:test module for running tests, and the supertest package sends requests straight into an Express application without taking a port.

Installation

npm install --save-dev supertest

The --save-dev flag records the package as a development dependency: the production server does not need it.

Why app.js is separate

In the project structure lesson the application went into src/app.js and starting it into src/server.js. Now this pays off: a test imports the ready application, and supertest starts it for the duration of the request.

A separate database for tests

Tests must not touch real posts. Create a .env.test file with an in-memory database — it disappears when the tests finish:

SESSION_SECRET=test-session-secret
JWT_SECRET=test-jwt-secret
DATABASE_FILE=:memory:

And add a script to package.json:

{
  "scripts": {
    "test": "node --env-file=.env.test --test"
  }
}

node --test finds files whose names end in .test.js by itself.

The first test

Create the file test/posts.test.js:

// test/posts.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import request from 'supertest';
import app from '../src/app.js';
 
test('the post list answers with an array', async () => {
  const response = await request(app).get('/api/posts');
 
  assert.equal(response.status, 200);
  assert.ok(Array.isArray(response.body));
});

request(app).get() sends a request and returns the response: the code in status, parsed JSON in body, HTML in text. The assert module checks expectations.

Running

npm test

The terminal shows a report: the test name with a ✔ mark, and at the bottom the number of passed and failed tests. If a test fails, the report shows which value was expected and which arrived.

More checks

It pays to write a test for every important case — especially errors:

// test/posts.test.js
test('a missing post gives 404', async () => {
  const response = await request(app).get('/api/posts/99999');
  assert.equal(response.status, 404);
});
 
test('creating without a token is forbidden', async () => {
  const response = await request(app)
    .post('/api/posts')
    .send({ title: 'Test', body: 'Text' });
 
  assert.equal(response.status, 401);
});
 
test('the blog home page opens', async () => {
  const response = await request(app).get('/posts');
 
  assert.equal(response.status, 200);
  assert.match(response.text, /All posts/);
});

The .send() method with an object sends the body as JSON and sets the Content-Type header itself.

Common mistake

Do not call app.listen() in src/app.js. Otherwise importing it into a test starts a real server: the port may be taken by another process, and npm test does not finish after the tests because the server keeps waiting for requests. Starting belongs only in src/server.js, which the tests do not import.

Thanks for reading!

Donate to the site — we work hard for you.