Recently, when working on a project I ran into a situation in which I needed to handle a complex object/data structure. This object had to be able to be saved into a database (dispersed over multiple different tables), retrieved, and reconstructed exactly as intended.
In order to verify things were built as intended, I created Unit Tests that would write and read this object into my database. But I did not want to use my development database for this.
Why? The traditional approach of using a development database for testing often leads to several issues: interference with development work, data corruption, inconsistent results, and overlapping tests affecting each other.
But what if there was a way to conduct these tests in a controlled, isolated, and repeatable environment, using a real database? 🤔
Enter Testcontainers!
Why Testcontainers?
Testcontainers is a very neat open source framework/project I just discovered. It enables developers to create unit tests using throwaway, lightweight instances of e.g. a database running in Docker containers.
This approach ensures that every test runs in a fresh, isolated environment, eliminating the problems associated with using a shared development database. No more conflicting tests, no more corrupt data, and consistent, reliable results every time you run your tests.
And it works for multiple languages, like Go, Python and Javascript and it supports many external dependencies, like Postgres (as I'm using in my case), MySQL, Git, several Cloud providers, Redis, Elastic and many more 🤯
Example
Look at the following code that I copied from the documentation of Testcontainers:
const { Client } = require("pg");
const { PostgreSqlContainer } = require("@testcontainers/postgresql");
const { createCustomerTable, createCustomer, getCustomers } = require("./customer-repository");
describe("Customer Repository", () => {
jest.setTimeout(60000);
let postgresContainer;
let postgresClient;
beforeAll(async () => {
postgresContainer = await new PostgreSqlContainer().start();
postgresClient = new Client({ connectionString: postgresContainer.getConnectionUri() });
await postgresClient.connect();
await createCustomerTable(postgresClient)
});
afterAll(async () => {
await postgresClient.end();
await postgresContainer.stop();
});
it("should create and return multiple customers", async () => {
const customer1 = { id: 1, name: "John Doe" };
const customer2 = { id: 2, name: "Jane Doe" };
await createCustomer(postgresClient, customer1);
await createCustomer(postgresClient, customer2);
const customers = await getCustomers(postgresClient);
expect(customers).toEqual([customer1, customer2]);
});
});
This code is a test that tests a customer repository and does a couple of actions against a real database. The amazing thing is that this test is not mocking anything, but always runs with fresh and new real database in a Docker container! So no more mocks or conflicting tests and a clean database every time you run your tests.
Try it out yourself
I think Testcontainers make running specific test cases easier, while having optimal isolation, consistency (and thus reliability) and simplicity (and thus better to maintain and manage).
So, check out → https://testcontainers.com/
Top comments (0)