Summary
I hacked RedwoodJS API tests to work with PostgreSQL in a disposable container via testcontainers
. I just got this working, so there are likely error conditions I don’t handle, and the code could be neater. For instance, my approach to the database URL environment variables could be made more precise.
However, since I wasn’t able to find any existing docs on how to do this, I figured what I have now is a step forward and worth sharing.
Background
I wanted to make sure I always had a clean database when running tests. RedwoodJS tries to clean up after itself, but sometimes that doesn’t work, and when you’re doing more in your database than just Prisma models, you have to figure out your own cleanup strategy. That can be difficult to get right, so I figured I’d just throw away the database entirely by using testcontainers. Testcontainers launches a dependency in a Docker container.
Implementation
api/$ yarn add testcontainers @testcontainers/postgresql
api/src/lib/test/testcontainers.ts
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';
let postgresContainer: StartedPostgreSqlContainer | undefined = undefined
export const initializeContainer = async () => {
postgresContainer = await new PostgreSqlContainer().start();
return postgresContainer
};
export const stopContainer = async () => {
if (postgresContainer) {
await postgresContainer.stop();
}
}
process.on('exit', stopContainer)
In api/jest.config.js
add:
globalSetup: '<rootDir>/api/src/lib/test/setup/jestGlobalSetup.ts',
globalTeardown: '<rootDir>/api/src/lib/test/setup/jestGlobalTeardown.ts',
This was a little hairy because it messed up the Redwoodjs presets. I addressed that in the below:
api/src/lib/test/setup/jestGlobalSetup.ts
:
import { initializeContainer } from "../testcontainers";
const rwJestPreset = require('@redwoodjs/testing/config/jest/api/jest-preset')
module.exports = async () => {
const postgresContainer = await initializeContainer()
const dbEnvVars = [
'DIRECT_URL',
'DATABASE_URL',
'TEST_DATABASE_URL',
'TEST_DIRECT_URL',
]
for (const envVar of dbEnvVars) {
process.env[envVar] = postgresContainer.getConnectionUri()
}
if (rwJestPreset.globalSetup) {
try {
const presetGlobalSetup = require(rwJestPreset.globalSetup)
return presetGlobalSetup()
} catch (e) {
// do nothing?
}
}
}
Then api/src/lib/test/setup/jestGlobalTeardown.ts
import { stopContainer } from "../testcontainers";
const rwJestPreset = require('@redwoodjs/testing/config/jest/api/jest-preset')
module.exports = async () => {
if (rwJestPreset.globalTeardown) {
try {
const presetGlobalTeardown = require(rwJestPreset.globalTeardown)
return presetGlobalTeardown()
} catch (e) {
// do nothing?
}
}
await stopContainer()
}
I hope this is useful for someone. Perhaps RedwoodJS can one day support testcontainers
out of the box.