hrtyy.dev

TestContainer testing with Jest, NodeJS.

Testing NodeJS application connects to PostgreSQL with Docker, Jest and TestContainer.

We want to test our nodejs application with jest. The application connects to PostgreSQL. If we can use containers in the test, that sounds great.

Actually, TestContainer is a library to realize that. The org says

Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.

But, they serves the same library in NodeJS. This is so useful.

1. Setup project

Setup node project in your any directory. We use sample directory here.

1yarn init
2// press enter until success.
3yarn add pg
4yarn add -D typescript ts-jest testcontainers @types/jest @types/pg
5mkdir __tests_
6// jest will search test cases in __tests__ directory by default.

Add simple test

__test__/add.test.ts
1test("sample", () => {
2 expect("1").toBe("1")
3})

and run test.

1yarn jest
2
3 PASS __tests__/add.test.ts
4 ✓ sample (1 ms)
5
6Test Suites: 1 passed, 1 total
7Tests: 1 passed, 1 total
8Snapshots: 0 total
9Time: 0.289 s
10Ran all test suites.
11✨ Done in 1.23s.

2. Setup TestContainers

TestContainers can be built specifying docker image tag or local Dockerfile. We go ahead with the latter way.

Dockerfile
1FROM postgres:11.5
2EXPOSE 5432
3COPY migration /docker-entrypoint-initdb.d

Choose postgres image tag and set COPY command to initialize db. Add a SQL file to migration directory.

migration/V1__helloworld.sql
1CREATE TABLE IF NOT EXISTS books
2(
3 id SERIAL PRIMARY KEY,
4 name VARCHAR(100) NOT NULL UNIQUE,
5 price INTEGER NOT NULL
6);

Next read this Dockerfile file to build TestContainer. Add testContainer.ts file to tests directory.

__tests__/testContainer.ts
1import {GenericContainer} from "testcontainers";
2import {Client} from "pg";
3
4export async function generatePgClientWithTestContainer(): Promise<Client> {
5 const container = await GenericContainer.fromDockerfile(__dirname + "/..").build();
6 const testContainer = await container
7 .withEnv("POSTGRES_USER", "sample")
8 .withEnv("POSTGRES_PASSWORD", "sample")
9 .withEnv("POSTGRES_DB", "sample_db")
10 .withExposedPorts(5432)
11 .start()
12
13 const client = new Client({
14 user: "sample",
15 password: "sample",
16 database: "sample_db",
17 host: testContainer.getHost(),
18 port: testContainer.getMappedPort(5432),
19 });
20
21 await client.connect();
22 return client;
23}

Some environments can be passed by withEnv. Postgres database server runs in the test container. To connect to it, we need host and port. getHost and getMappedPort are the methods to get them.

Replace test code.

__tests__/add.test.ts
1import {generatePgClientWithTestContainer} from "./testContainer";
2
3jest.setTimeout(20000);
4test("container", async () => {
5 const client = await generatePgClientWithTestContainer();
6
7 await client.query("INSERT INTO books (name, price) VALUES ('book_A', 100), ('book_B', 200);");
8 const result = await client.query("SELECT * from books");
9
10 expect(result.rows[0].price + result.rows[1].price).toBe(300);
11 await client.end();
12});

The client connects to database server hosted by TestContainer. Thanks to TestContainer, testing application connects to database becomes easy.

Before testing the above code, edit package.json.

package.json
1+ "jest": {
2+ "modulePathIgnorePatterns": ["testContainer.ts"],
3+ "transform": {
4+ "^.+\\.ts$": "ts-jest"
5+ }
6+ }

Then, test again.

1yarn jest
2
3 PASS __tests__/add.test.ts (9.499 s)
4 ✓ container (8955 ms)
5
6Test Suites: 1 passed, 1 total
7Tests: 1 passed, 1 total
8Snapshots: 0 total
9Time: 9.562 s
10Ran all test suites.
11✨ Done in 10.44s.