skip to content
Eduim

Express testing p1: setup test environment

/ 6 min read

Intro

Today I bring a topic that is going to help you to boost your productivity in the long term. If you are already working with Express in a team and you don’t have any tests you should have notice at this point that it’s really easy to mess up the code base and break working features. In this post I will show you how to setup a test environment for your Express server and in the next post I will show you

  • Setting up a Testing Environment: Learn how to configure your development environment for testing, including tools like Jest, Supertest and Docker.
  • Unit Testing: Dive into the fundamentals of unit testing, where individual components of your Express application are isolated and tested in isolation.
  • Integration Testing: Explore techniques for testing the interaction between different components of your application, including database interactions and external API calls.

For the exercise we have a really simple app with two resouces, users and posts, it’s the tipical app for learn. It has MVC structure, with auth and error handler middlewares.

We have 3 types of tests:

  • Unit Testing: Tests individual units or components in isolation to ensure they perform as expected.
  • Integration Testing: Validates the interaction between integrated components to ensure they work together seamlessly.
  • End-to-End Testing (E2E Testing): Tests the entire application from start to finish, simulating real user scenarios and interactions.

In our case we are going only to perform unit testing and integration with the database/other services not paid. In my experience integration testing is the easiest tests because it’s almost test your API documentation and I only use the unit testing for special functions like the auth/error handler middlewares.

Installing dependencies

The first tool we need it’s jest, it’s not supporting Typescript out of the box we’ll need to add other dependencies like the types, add them with pnpm add -D jest @types/jest ts-jest, the last package is a preprocessor for jest that allows to transpile TS on the fly and have source-map support build in.

Add some configuration to the jest.config.js if you don’t have it add the file

module.exports = {
	roots: ["<rootDir>/src"],
	testMatch: ["**/__tests__/**/*.+(ts|tsx|js)", "**/?(*.)+(spec|test).+(ts|tsx|js)"],
	transform: {
		"^.+\\.(ts|tsx)$": "ts-jest",
	},
};

I’ll assume that you have the source code in the src folder if you don’t just specify where it’s going to be in roots. To testMatch config is a glob pattern matcher for discovering test files and finally we specify to use ts-jest to transform all TS files.

Finally let’s test it, add {"test": "jest"} to package.json. If you run the script it will complain for the lack of tests. Add a dummy test in __tests__/example.test.ts inside src:

const sum = (a: number, b: number) => {
	return a + b;
};
test("adds 1 + 2 to equal 3", () => {
	expect(sum(1, 2)).toBe(3);
});

Environment setup

First of all, let’s explain why do we need a test environment. Ideally you don’t want to mix with production code in your development environment, another reason in this case is that we are going to restart the environment every time we make a test to avoid problems mixing other tests. Usually with unit testing you can avoid to call your production services just mocking them, but we are going to use a real db service for this case. Instead of calling the real one we are going to create a testing db.

test-flow

We are going to use docker for helping us. Let’s assume that we already have a docker postgres image running for development, like this in a docker-compose.yml:

version: "3.8"
services:
  db:
    image: postgres
    restart: always
    container_name: db
    ports:
      - "5433:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydatabase

Create another one docker-compose.test.yml that is going to be called in the tests:

version: "3.8"
services:
  test-db:
    image: postgres:13
    restart: always
    container_name: test-db
    ports:
      - 5432:5432
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydatabase

As you can see the env variables are the same but the port and the names of the containers are different, this is important to let docker know that are different services and containers. Try to run it with docker compose -f docker-compose.test.yml up -d, the -f flag is to specify the path to the docker-compose.test.yml file.

test-db refers to the serivce name not the container name

We can automate the setup and tear down of the db test with 2 scripts in the package.json.

{
	"db:test:up": "docker compose -f docker-compose.test.yml up -d",
	"db:test:stop": "docker compose -f docker-compose.test.yml rm -s -f -v"
}

-s flag is to stop the containers, -f flag to force remove, -v flag is to remove the volumes, rm to remove containers

With this we have the db test ready. We also need to specify in the express app where to point. We usually write the address in the .env file. We can create another one .env.test for testing, change the port from the .env and it should do it.

Now in the scripts we need to specify to use .env.test by default it’s going to point to .env. For that we can use a package called dotenv-cli.

Finally in this test setup although we have the database every time we run the tests we are creating it from scratch, that means that we need to run the migrations of the schema, in this case we are using PrismaORM to help us.

Scripts

The final process:

  • Delete previos dbs
  • Create the db container
  • Migrate the db
  • Run the tests

We can include the following scripts in the package.json, the first one is to run the schema migrations in the db with the correct .env. the second one is going to make sure that we create a new DB each time and the last one is just to run the tests

{
	"prisma:test:deploy": "dotenv -e .env.test prisma migrate deploy",
	"db:test:restart": "pnpm db:test:stop && pnpm db:test:up && sleep 2 && yarn prisma:test:deploy",
	"pretest": "pnpm db:test:restart",
	"test": "jest"
}

the sleep 2 command is to give docker time to start the db and run the migrations without problems. The pretest is a hook that indicates the script that should run before the test

With this you should have the environment set up for the tests. In this post we’ll explore how to actually make the tests.