Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testcontainers example #491

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions testcontainers/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/

**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.next
**/.cache
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose.y*ml
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
**/build
**/dist
LICENSE
README.md

*.js
1 change: 1 addition & 0 deletions testcontainers/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
REDIS_URL=
3 changes: 3 additions & 0 deletions testcontainers/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
node_modules/
*.js
38 changes: 38 additions & 0 deletions testcontainers/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# syntax=docker/dockerfile:1

# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/

# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7

ARG NODE_VERSION=20.13.1

FROM node:${NODE_VERSION}-alpine

# Use production node environment by default.
ENV NODE_ENV production


WORKDIR /usr/src/app

# Download dependencies as a separate step to take advantage of Docker's caching.
# Leverage a cache mount to /root/.npm to speed up subsequent builds.
# Leverage a bind mounts to package.json and package-lock.json to avoid having to copy them into
# into this layer.
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci --omit=dev

# Run the application as a non-root user.
USER node

# Copy the rest of the source files into the image.
COPY . .

# Expose the port that the application listens on.
EXPOSE 3000

# Run the application.
CMD node index.js
19 changes: 19 additions & 0 deletions testcontainers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Docker Compose Testcontainers

## Why Testcontainers?

Testcontainers is a tool for creating lightweight, throwaway instances of common databases or anything that can run in a Docker container. In addition to testing, you can leverage the Testcontainers [Docker Compose Feature](https://node.testcontainers.org/features/compose/) to perform **local development** in scenarios where you cannot install the Docker engine or need **cloud workloads** to offload resource-intensive tasks.

This example demonstrates how to use Testcontainers for a Node.js environment. With Testcontainers Desktop, you can develop against [Testcontainers Cloud](https://testcontainers.com/cloud/) or the [Testcontainers embedded runtime](https://newsletter.testcontainers.com/announcements/adopt-testcontainers-desktop-as-your-container-runtime-early-access) to see Testcontainers capabilities.

## Prerequisites

- [Node.js](https://nodejs.org/en/download/)
- [Docker](https://docs.docker.com/get-docker/)
- [Testcontainers Desktop](https://testcontainers.com/desktop/)

## Running the app

The app checks if the `REDIS_URL` environment variable is set. If so, it connects to the specified Redis instance. If `REDIS_URL` is not set, it uses testcontainers to create a Redis instance using the Docker Compose file `redis.yaml`. Simply run `npm run dev` to start the app.

Additionally you can also start this example without Testcontainers by setting the `REDIS_URL` environment variable to a Redis instance. This is already prepared in the `compose.yaml` file, so you can just run `docker compose up` to try it out.
15 changes: 15 additions & 0 deletions testcontainers/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
include:
- path: redis.yaml

services:
server:
build:
context: .
init: true
environment:
NODE_ENV: production
REDIS_URL: ${REDIS_URL}
ports:
- 3000:3000
depends_on:
- redis
22 changes: 22 additions & 0 deletions testcontainers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import express from "express"
import { setupRedis } from "./setupRedis.js"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, shall we get rid of .js? Or it's necessary? 🤔

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, b/c we use mjs Syntax


const app = express()
app.use(express.json())

const client = await setupRedis()

app.get("/", async (req, res) => {
try {
const newVisitorCount = await client.incr("visitorCount")
res.send(`You are visitor number ${newVisitorCount}`)
} catch (error) {
console.error("Error accessing Redis:", error)
res.status(500).send("Server error")
}
})

const PORT = 3000
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`)
})
24 changes: 24 additions & 0 deletions testcontainers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "testcontainers-js-compose",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"dev": "nodemon --exec node --loader ts-node/esm index.ts"
},
"keywords": [],
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.19.2",
"redis": "^4.6.14"
},
"devDependencies": {
"@testcontainers/redis": "^10.9.0",
"@types/express": "^4.17.21",
"nodemon": "^3.1.3",
"testcontainers": "^10.9.0",
"ts-node": "^10.9.2",
"typescript": "^4.9.4"
}
}
11 changes: 11 additions & 0 deletions testcontainers/redis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
redis:
container_name: redis
image: redis:latest
ports:
- "6379:6379"
volumes:
- redis-data:/data

volumes:
redis-data:
38 changes: 38 additions & 0 deletions testcontainers/setupRedis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import redis, { type RedisClientType } from "redis"
type RedisClient = RedisClientType<any, any>

export async function setupRedis(): Promise<RedisClient> {
if (process.env.REDIS_URL) {
return connectToRedis(process.env.REDIS_URL)
}

const testcontainers = await import("testcontainers")

const environment = await new testcontainers.DockerComposeEnvironment(".", "redis.yaml")
.withWaitStrategy("redis", testcontainers.Wait.forLogMessage("Ready to accept connections"))
.withNoRecreate()
.up()

const redisPort = environment.getContainer("redis").getMappedPort(6379)
const redisConnectionString = `redis://localhost:${redisPort}`
const client = await connectToRedis(redisConnectionString)

// const testContainersRedis = await import("@testcontainers/redis")
// const container = await new testContainersRedis.RedisContainer().withReuse().start()
// const client = await connectToRedis(container.getConnectionUrl())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we perhaps get rid of that?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!


process.on("SIGINT", async () => {
await client.quit()
process.exit()
})

return client
}

async function connectToRedis(url: string): Promise<RedisClient> {
const client = redis.createClient({ url }) as RedisClient
client.on("error", (err) => console.log("Redis Client Error", err))
await client.connect()

return client
}
10 changes: 10 additions & 0 deletions testcontainers/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true
}
}