Server Testing Patterns for NodeJS

Hasan Gökçe
3 min readDec 10, 2020

1. Introduction

Most of the time, the developer has its own decisions about how in-depth to write a test. Each test increases time for the testing cycle and if the code changes, it might require maintenance.

As we develop the application, we may decide to reduce feature level tests with equal coverage at lower level tests. How can we make a decision? One thing is to ask ourselves :

What would you prefer?

  • A slow feature test
  • A faster server test that doesn’t test UI

The aim should be picking a set of tests that give the best combination for that context. In terms of reliability, speed, and completeness.

In this post, we are going to be using server-level testing technologies for an Express server.

Server tests generally include:

  • header key and values
  • status codes
  • the body of the response

Let’s look at the differences between a feature-level test and a server-level test:

a feature-level test

During a test-driven development, when the test above fails due to a non-existent server, the developer decides to dive into the server-level and continues the TDD process.

a server level test

2. Status Codes

One use of TDD at the server level is to be sure that the status code returned as expected. With the returned correct status, we obtain confidence at the most basic level for saying “Server is functioning correctly.” A test suite with HTTP status codes provides a quick check for a new feature. For example, checking not authorized(401) or not found (404). (A complete list can be found at httpstatuses.com)

To verify, we make an assertion with the status codes:

assert.equal(response.status, 200);

If we use the “red, green, refactor” approach, at this phase, we expect that the test fails (“red”). Then we do implementations to pass the test (“green”). And we refactor our code if needed (“refactor”).

index-test.js
index.js

3. Response Content

Above, we checked the correctness of the status code. Now, it is time to check response’s content, we are going to look at HTML response.

When designing tests, it is important writing the test for intendent and unintendent user behavior.

  • “Happy path” — expected use cases for the application.
  • “Sad path” — unexpected or invalid use of the application

We use assert.include from Chai library, to check HTML content on the response. As an example; after clicking “My Profile” button, you receive a response content similar to this:

response.text = ‘<div><div id=”my-name”>My Name</div></div>’;

We can catch “My Name” and check with the following code:

assert.include(parseTextFromHTML(response.text, ‘#my-name’), “My Name”); //True

We could also write a separate test for “sad path”. Think that there is no profile page yet. Therefore we shouldn’t see the My Name on the page. For this, notInclude verifies that there is any text including “My Name”.

assert.notInclude(parseTextFromHTML(response.text, '#my-name'), "Your Name"); //True

An important point that we used a method to check if an element includes a text (parseTextFromHTML). This was for our example. You can use a lot of methods of jsdom to select elements on the response. This helper’s aim was it. But you can write another separate helper for accessing other attributes.

4. Refactoring: Route Parameters

We mentioned a home with ‘Message App’ text. What if it is a profile page in which every user name is different? One straightforward solution might be writing hard code like ‘profile/alice’ = ‘Welcome Alice!’ and ‘profile/bob’ = ‘Welcome Bob!’.

There are repetitive codes, this is an urgent sign we need to refactor the code. By adding URL parameters, we can access server-generated usernames.

‘profile/:username’ = `Welcome ${req.params.username}!`
Before
After

5. Refactoring: Handlebars

8. Sources

--

--