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.
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.