Fortunately, deploying applications in containers is becoming more commonplace. It's likely that your CI pipeline already has first-class support for running them. However, managing containers programmatically from your application isn't as straightforward. For this reason I've created testcontainers, which I hope simplifies this process.
Code here: https://github.com/cristianrgreco/testcontainers-node.
Imagine you have a requirement to create a cache; you decide on the following interface:
interface Cache {
get(key: string): Promise<string>;
set(key: string, value: string): Promise<void>;
}
You decide on a Redis implementation. With testcontainers it's easy to acceptance-test drive the implementation. You could have the following: const redis = require("async-redis");
const { GenericContainer } = require("testcontainers");
describe("RedisCache", () => {
let container;
let cache;
beforeAll(async () => {
container = await new GenericContainer("redis:4.0")
.withExposedPorts(6379)
.start();
const redisClient = redis.createClient(container.getMappedPort(6379));
cache = new RedisCache(redisClient);
});
afterAll(async () => {
await container.stop();
});
it("should cache a value", async () => {
await cache.set("key", "value");
expect(await cache.get("key")).toBe("value");
});
});
By having the application itself manage its own infrastructure dependencies, you are able to debug integration code via the IDE.Running tests against the system and all of it's integrations is now as easy as an "npm test".