Page Content

Tutorials

How to Test a Node.js Application? & Does Jest use nodejs?

Test a Node.js Application

The process of developing software includes testing. Checking that your application is always operating as intended is made simple by setting up an automated test suite. To make sure their application is functioning as they want it to, programmers frequently run code that tests it while they make changes. This may be automated to save a ton of time. Developers can feel confident in their codebase, especially when deploying to production, because regular testing after creating new code guarantees that new changes don’t disrupt existing functionalities.

Jest Testing Framework

You can develop a test suite for your Node.js apps using the Jest testing framework. It makes it simple to verify that your Node.js application is consistently functioning as intended and enables you to test your applications.

Setting up Jest: You must install the module using npm before you can begin using Jest:

npm i jest@23.6.0 

The next step is to write a test script in package.json. Since the script is called jest, you can run the Jest test suite with npm test.

{
    "scripts": {
        "test": "jest" 
    }
}

The next step is to build a test suite file, which is a file in your project that has the extension .test.js. Jest can locate and execute the test suites for your project with the help of this file extension.

Creating a Test Case: You can add a test case to a test suite using the test function. Jest provides various functions as global variables in your test suite files, including test. The first argument to test is the name of your test case, and the second argument is the test function itself. For assertions, you’ll use the expect function. For instance, expect(total).toBe(13) asserts that total is 13. Jest will generate useful error messages for you if an assertion fails.

Configuring Jest for Node.js: Jest assumes that it will operate in the browser by default. To use Jest with Node.js, you’ll need to configure it by adding a jest property in package.json, setting testEnvironment to node:

{
    "jest": {
        "testEnvironment": "node" 
    }
}

Testing Asynchronous Code

Asynchronous code must be tested when handling actions such as database queries or HTTP API requests. The non-blocking nature of Node.js means that your code won’t necessarily run in the order you might anticipate.

Using Callbacks (with done): The test function for the first test case takes a done parameter for testing asynchronous code using callbacks (with done). With the use of this option, Jest is informed that the test method includes asynchronous code and that it will not know if the test passed or failed until done() is invoked.

test('Should add two numbers (callback)', (done) => {
    add(2, 3).then((sum) => { // Assuming 'add' is an async function returning a promise
        expect(sum).toBe(5);
        done(); // Call done() to signal test completion
    });
});

Using async/await: For a more streamlined syntax, Jest test cases can also make use of async/await. In order to make sure that Jest waits for asynchronous processes to finish, the test case function is defined with async and uses await. This makes writing and maintaining sophisticated asynchronous code considerably simpler by making it appear to be standard synchronous code.

test('Should add two numbers async/await', async () => {
    const sum = await add(2, 3); // Await the result of the asynchronous 'add' function
    expect(sum).toBe(5);
});

When it comes to testing asynchronous actions, the callback and async/await methodologies are both operationally equivalent.

Testing Express Apps with Supertest

To make it simple for you to test your Express apps, the Express team developed Supertest. It streamlines the testing of HTTP applications, which formerly required manually starting a server, sending requests, and then deciphering the answer.

Installation: First, install the Supertest module:

npm i supertest@3.4.1 

Making Requests and Assertions: Supertest allows you to provide your Express application to request() in order to make requests and assertions. Supertest methods can then be chained together to fulfill your tests’ requirements and send HTTP requests. For instance, using a POST request to /users to test the signup of a new user:

const request = require('supertest');
const app = require('../src/app'); // Assuming your Express app is exported from this file
test('Should signup a new user', async () => {
    await request(app).post('/users').send({
        name: 'Andrew',
        email: 'andrew@example.com',
        password: 'MyPass777!'
    }).expect(201); // Assert that the response status code is 201 (Created)
});

Using .post() to make a POST request, .send() to transmit JSON data, and .expect(201) to assert a 201 status code are all demonstrated in this example. Additionally, you can use GET requests and assert on the body of the response:

test('Should get profile for user', async () => {
    await request(app)
        .get('/users/me')
        .set('Authorization', `Bearer ${userOne.tokens.token}`) // Example for authenticated requests
        .send()
        .expect(200); // Expect a 200 OK status
});

By removing the requirement for manual inspections, Supertest enables you to assert directly on status codes, headers, and the body. Other assertion libraries, such as expect, can likewise be used to supply a function to .expect() for bespoke assertions.

Test Lifecycle (Setup/Teardown)

Jest has lifecycle routines that let you set up your suite of tests. Before and after tests run, these are essential for seeding data, maintaining the test environment, and cleaning up resources.

Test frameworks such as Mocha frequently use the four primary lifecycle services that Jest offers, which are:

  • beforeEach(fn): Before every test case in the current describe block, the code beforeEach(fn) is executed. For each test, this is helpful for resetting the environment or test data.
  • afterEach(fn): Following each test case in the current describe block, the afterEach() function executes some code. helpful for removing temporary files and other resources produced by particular tests.
  • beforeAll(fn) (or before): Before all tests in the current describe block start, some code is run once using beforeAll(fn) (or before). Perfect for establishing a database connection or generating test data that will be shared by all tests.
  • afterAll(fn) (or after): Executes a single piece of code following the completion of every test in the describe block. helpful for terminating shared resources, such as a database connection.

Example using beforeEach and afterEach: Imagine testing a TODO module where each test needs a fresh Todos object and a clean CSV file.

const Todos = require('./index');
const assert = require('assert').strict;
const fs = require('fs');
describe("saveToFile()", function() {
    // This runs before EACH 'it' test within this describe block
    beforeEach(function() {
        this.todos = new Todos(); // Create a new Todos object for each test
        this.todos.add("save a CSV");
    });
    // This runs after EACH 'it' test within this describe block
    afterEach(function() {
        // Clean up the CSV file if it was created during the test
        if (fs.existsSync('todos.csv')) {
            fs.unlinkSync('todos.csv');
        }
    });
    it("should save a single TODO", async function() {
        await this.todos.saveToFile();
        assert.strictEqual(fs.existsSync('todos.csv'), true);
        let expectedFileContents = "Title,Completed\nsave a CSV,false\n";
        let content = fs.readFileSync("todos.csv").toString();
        assert.strictEqual(content, expectedFileContents);
    });
    it("should save a single TODO that's completed", async function() {
        this.todos.complete("save a CSV"); // Modify the Todos object created in beforeEach
        await this.todos.saveToFile();
        assert.strictEqual(fs.existsSync('todos.csv'), true);
        let expectedFileContents = "Title,Completed\nsave a CSV,true\n";
        let content = fs.readFileSync("todos.csv").toString();
        assert.strictEqual(content, expectedFileContents);
    });
});

This setup significantly reduces duplicated setup and teardown code, making test cases more readable and maintainable. The this object in beforeEach() refers to the same this object in it(), allowing for shared context.

Your Node.js apps will be more dependable, stable, and manageable over time if you use these sophisticated testing strategies.

Index