Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

dockest

erikengervall6.8kMIT3.1.0TypeScript support: included

Dockest is an integration testing tool aimed at alleviating the process of evaluating unit tests whilst running multi-container Docker applications.

docker, docker-compose, jest, nodejs, node, integration testing

readme

Dockest

Dockest is an integration testing tool aimed at alleviating the process of evaluating unit tests whilst running multi-container Docker applications.

dockest logo



licence npm downloads licence licence snyk

Table of contents

Introduction

Motivation

The original motivation for Dockest, along with real world examples, can be read in this blog article.

Dockest was born out of frustration and with a vision to make developers’ lives slightly less miserable.

Dockest provides an abstraction for your Docker services’ lifecycles during integration testing, freeing developers from convoluted and flaky shell scripts. Adopting Dockest is super easy regardless if you’ve got existing tests or not and doesn’t necessarily require additional CI pipeline steps.

Why Dockest

The value that Dockest provides over e.g. plain docker-compose is that it figures out the connectivity and responsiveness status of each individual service (either synchronously or asynchronously) and once all services are ready the tests run.

Example use cases

Dockest can be used in a variety of use cases and situations, some of which can be found under packages/examples.

AWS CodeBuild

What is AWS CodeBuild?

AWS CodeBuild is a fully managed continuous integration service that compiles source code, runs tests, and produces software packages that are ready to deploy.

Cool, can I run it locally?

You can now locally test and debug your AWS CodeBuild builds using the new CodeBuild local agent.

Node.js to Node.js

Dockest can also build and run application services as part of your integration tests.

Basic Usage

System requirements

In order to run Dockest, there's a few system requirements:

  • Dockest uses Jest's programmatic CLI and requires Jest v20.0.0 or newer to work
  • Docker
  • Docker Compose ("On desktop systems like Docker Desktop for Mac and Windows, Docker Compose is included as part of those desktop installs.")

Install

yarn add --dev dockest
# npm install --save-dev dockest

Application code

// cache.ts
export const cacheKey = 'arbitraryNumberKey';

export const setCache = (redisClient: Redis, arbitraryNumber: number) => {
  redisClient.set(cacheKey, arbitraryNumber);
};

Unit tests

// cache.spec.ts
import Redis from 'ioredis'; // ... or client of choice
import { cacheKey, setCache } from './cache';

const redisClient = new Redis({
  host: 'localhost',
  port: 6379, // Match with configuration in docker-compose.yml
});

it('should cache an arbitrary number', async () => {
  const arbitraryNumber = 5;

  await setCache(redisClient, arbitraryNumber);

  const cachedValue = await redisClient.get(cacheKey);
  expect(cachedValue).toEqual(arbitraryNumber);
});

Dockest integration tests

Transform unit test into an integration test by creating a docker-compose.yml and dockest.ts file.

Important note for the Compose file

Dockest expects services' ports to be defined using long format and works best with high versions of docker-compose (i.e. 3.7 or higher)

# docker-compose.yml
version: '3.8'

services:
  myRedis:
    image: redis:5.0.3-alpine
    ports:
      - published: 6379
        target: 6379
// dockest.ts
import { Dockest } from 'dockest';

const dockest = new Dockest();

// Specify the services from the Compose file that should be included in the integration test
const dockestServices = [
  {
    serviceName: 'myRedis', // Must match a service in the Compose file
  },
];

dockest.run(dockestServices);

Configure scripts

Configure package.json to run dockest.ts. ts-node is recommended for TypeScript projects.

{
  "scripts": {
    "test": "ts-node ./dockest"
  },
  "devDependencies": {
    "dockest": "...",
    "ts-node": "..."
  }
}

Run

Finally, run the tests:

yarn test

Development

Publishing a new version

Prep

  • Decide on a version. Let's reference it as v1.2.3
    • Append -alpha.0 or -beta.0 to create a tagged release
  • Create release branch git checkout -b "release-v1.2.3"
  • Run yarn lerna version --no-push --no-git-tag-version
  • Merge version updates into main branch
  • Create a new release at https://github.com/erikengervall/dockest/releases/new and let the CI do the rest

Contributing

Setup and testing

This is a monorepo using lerna, meaning all scripts can be run from root.

yarn prep will executes the necessary scripts to install dependencies for all packages (including root) as well as build whatever needs building.

yarn dev:link will link the library source to each example, making developing a smoother experience.

API Reference

DockestOpts

import { Dockest } from 'dockest';

const { run } = new Dockest(opts);

DockestOpts

DockestOpts is optional, i.e. the dockest constructor can be called without arguments.

DockestOpts structure:

property type default
composeFile string docker-compose.yml
composeOpts object see paragraph on composeOpts
debug boolean false
dumpErrors boolean false
exitHandler function null
jestLib object require('jest')
jestOpts object {}
logLevel object logLevel.INFO, i.e. 3
runInBand boolean true

DockestOpts.composeFile

Compose file(s) with services to use while running tests

DockestOpts.composeOpts

composeOpts structure:

property desription type default
alwaysRecreateDeps Recreate dependent containers. Incompatible with --no-recreate boolean false
build Build images before starting containers boolean false
forceRecreate Recreate containers even if their configuration and image haven't changed boolean false
noBuild Don't build an image, even if it's missing boolean false
noColor Produce monochrome output boolean false
noDeps Don't start linked services boolean false
noRecreate If containers already exist, don't recreate them. Incompatible with --force-recreate and -V boolean false
quietPull Pull without printing progress information boolean false

Forwards options to docker-compose up, Docker's docs.

DockestOpts.debug

Pauses Dockest just before executing Jest. Useful for more rapid development using Jest manually

DockestOpts.dumpErrors

Serializes errors and dumps them in dockest-error.json. Useful for debugging.

DockestOpts.exitHandler

Callback that will run before exit. Received one argument of type { type: string, code?: number, signal?: any, error?: Error, reason?: any, p?: any }

DockestOpts.jestLib

The Jest library itself, typically passed as { lib: require('jest') }. If omitted, Dockest will attempt to require Jest from your application's dependencies. If absent, Dockest will use it's own version.

DockestOpts.jestOpts

Jest's CLI options, an exhaustive list of CLI-options can be found in Jest's documentation

DockestOpts.logLevel

Decides how much logging will occur. Each level represents a number ranging from 0-4

DockestOpts.runInBand [boolean]

Initializes and runs the Runners in sequence. Disabling this could increase performance

Note: Jest runs tests in parallel per default, which is why Dockest defaults runInBand to true. This will cause jest to run sequentially in order to avoid race conditions for I/O operations. This may lead to longer runtimes.

Run

import { Dockest } from 'dockest';

const { run } = new Dockest();

const dockestServices = [
  {
    serviceName: 'service1',
    commands: ['echo "Hello name1 🌊"'],
    dependents: [
      {
        serviceName: 'service2',
      },
    ],
    readinessCheck: () => Promise.resolve(),
  },
];

run(dockestServices);

DockestService

Dockest services are meant to map to services declared in the Compose file(s)

DockestService structure:

property type default
name string property is required
commands (string | function)[] => string[] []
dependents DockestService[] []
readinessCheck function () => Promise.resolve()

DockestService.name

Service name that matches the corresponding service in your Compose file

DockestService.commands

Bash scripts that will run once the service is ready. E.g. database migrations.

Can either be a string, or a function that generates a string. The function is fed the container id of the service.

DockestService.dependents

dependents are Dockest services that are are dependent on the parent service.

For example, the following code

const dockestServices = [
  {
    serviceName: 'service1',
    dependents: [
      {
        serviceName: 'service2',
      },
    ],
  },
];

will ensure that service1 starts up and is fully responsive before even attempting to start service2.

Why not rely on the Docker File service configuration options depends_on?

Docker's docs explains this very neatly:

version: '3.8'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

depends_on does not wait for db and redis to be “ready” before starting web - only until they have been started.

DockestService.readinessCheck

The Dockest Service's readinessCheck function helps determining a service's readiness (or "responsiveness") by, for example, querying a database using select 1. The readinessCheck function receive the corresponding Compose service configuration from the Compose file as first argument and the containerId as the second.

The readinessCheck takes a single argument in form of an object.

const dockestServices = [
  {
    serviceName: 'service1',
    readinessCheck: async ({
      containerId,
      defaultReadinessChecks: { postgres, redis, web },
      dockerComposeFileService: { ports },
      logger,
    }) => {
      // implement your readinessCheck...
    },
  },
];

readinessCheck structure:

property description
containerId The Docker container's id.
defaultReadinessChecks Dockest exposes a few default readinessChecks that developers can use. These are plug-and-play async functions that will attempt to establish responsiveness towards a service.
dockerComposeFileService This is an object representation of your service's information from the Compose file.
logger An instance, specific to this particular Dockest Service (internally known as Runner), of the internal Dockest logger. Using this logger will prettify and contextualize logs with e.g. the serviceName.

defaultReadinessChecks

defaultReadinessChecks.postgres

The default readiness check for PostgreSQL is based on this image which expects certain environment variables.

# docker-compose.yml
version: '3.8'

services:
  postgres: # (1)
    image: postgres:9.6-alpine
    ports:
      - published: 5432
        target: 5432
    environment: # (2)
      POSTGRES_DB: baby
      POSTGRES_USER: dont
      POSTGRES_PASSWORD: hurtme
// dockest.ts
import { Dockest } from 'dockest';

const { run } = new Dockest();

run([
  {
    serviceName: 'postgres', // must match (1)
    readinessCheck: async ({
      defaultReadinessChecks: { postgres },
      dockerComposeFileService: {
        environment: { POSTGRES_DB, POSTGRES_USER }, // must match (2)
      },
    }) => postgres({ POSTGRES_DB, POSTGRES_USER }),
  },
]);

defaultReadinessChecks.redis

The default readiness check for Redis is based on this image which is plug-and-play.

# docker-compose.yml
version: '3.8'

services:
  redis: # (1)
    image: redis:5.0.3-alpine
    ports:
      - published: 6379
        target: 6379
// dockest.ts
import { Dockest } from 'dockest';

const { run } = new Dockest();

run([
  {
    serviceName: 'redis', // must match (1)
    readinessCheck: ({ defaultReadinessChecks: { redis } }) => redis(),
  },
]);

defaultReadinessChecks.web [WIP]

Requires wget. The image would most likely be a self-built web service.

The exact use case should be fleshed out.

// dockest.ts
import { Dockest } from 'dockest';

const { run } = new Dockest();

run([
  {
    serviceName: 'web', // must match (1)
    readinessCheck: async ({ defaultReadinessChecks: { web } }) => web(),
  },
]);

Utils

logLevel object

Helper constant for DockestOpts

import { logLevel } from 'dockest';

console.log(logLevel);

// {
//   NOTHING: 0,
//   ERROR: 1,
//   WARN: 2,
//   INFO: 3,
//   DEBUG: 4
// }

sleep function

Sleeps for X milliseconds.

import { sleep } from 'dockest';

const program = async () => {
  await sleep(1337);
};

program();

sleepWithLog function

Sleeps for X seconds, printing a message each second with the progress.

import { sleepWithLog } from 'dockest';

const program = async () => {
  await sleepWithLog(13, 'sleeping is cool');
};

program();

execa function

Exposes the internal wrapper of the execa library.

import { execa } from 'dockest';

const opts = {
  logPrefix,
  logStdout,
  execaOpts,
  runner,
};

const program = async () => {
  await execa(`echo "hello :wave:"`, opts);
};

program();

opts structure:

property description type default
logPrefix Prefixes logs string '[Shell]'
logStdout Prints stdout from the child process boolean false
execaOpts Options passed to the execa function object {}
runner Internal representation of a DockestService. Ignore this Runner -

Versioned Documentation

Acknowledgements

Thanks to Juan Lulkin for the logo ❤️

Thanks to Laurin Quast for great ideas and contributions 💙

License

MIT

changelog

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[3.0.1] - 2022-02-02

Fixed

  • Add support for handling the trimming of version field from docker compose config command #284

[3.0.0] - 2021-10-12

Changed

  • Website: Migrated docs from gh-pages to Netlify
  • Website: Removed versioning from site itself. Versions will instead be available on a separate page on the website pointing towards source docs in GitHub for that version.

[3.0.0-beta.0] - 2021-09-13

Added

  • Add configurable readiness retry count #200
  • Overhaul readiness checks & introduce dockest/readiness-check #212
  • Add container log collection #167
  • feat: replace dependents with dependsOn option #213
  • Migrated website from docusaurus 1 to docusaurus 2 (beta.6) #264

Fixed

  • Correctly handle services that have no ports defined #210

Changed

  • Make Dockest opts optional #263

[2.1.0] - 2020-10-22

Added

  • Added dependencies io-ts and fp-ts

Fixed

  • docker-compose file decoding #184

[2.0.2] - 2020-04-18

Added

  • Support for docker-in-docker #166
  • New helper resolveServiceAddress #162
  • Added option to skip port connectivity check #163

[2.0.1] - 2020-03-12

Fixed

  • Automatically transform legacy port mappings to the new format #154

Changed

  • If no ports are provided for a service, the checkConnection step is skipped (a log message will however appear) #154

[2.0.0] - 2020-03-01

Other than the changes below, there's everything that has been introduced from the previous pre-releases.

The reasoning for this major release can be read in the PR, but also right here:

I feel that this library has matured into its second major release. The parts I felt uncomfortable with have been removed (e.g. the initial concept of Runners) and the library has evolved into something a lot more user-friendly.

It should be major because the interface has completely changed towards the user.

It should be bumped at this point in time because the interface has reached a stable state and there has been extensive local testing.

Changed

  • Renamed healthchecks to readinessChecks to avoid confusion with Docker's healthcheck
  • Improved documentation

[2.0.0-beta.2] - 2020-02-12

Changed

  • Adopted RxJS to listen in on docker events, greatly improving the responsiveness and sturdiness of all checks #136
  • Support function as a command with containerId as argument #133

[2.0.0-beta.1] - 2020-02-01

Added

  • Expose sleepWithLog
  • Move chalk to dependencies

Changed

  • Bump dependencies throughout the repo
  • Internals: Introduce a Mutables field that contains mutable fields, e.g. runners

[2.0.0-beta.0] - 2020-01-24

Added

  • Added the option to pass dependent Dockest Services to Dockest Services. Essentially creating a more robust depends_on
  • Added Dockest options for forwarding compose CLI options for docker-compose <opts> up. E.g. --build or --force-recreate

Changed

  • Changed Dockest Services healthchecks prop to a healthcheck prop. Instead of passing an array of functions, simply implement a single healthcheck function that'll be called recursively until successful or times out. The healthcheck function is also fed default healthcheck functions.
  • Made documentation more verbose and descriptive

[2.0.0-alpha.3] - 2019-12-18

Added

  • GitHub Actions: Pipeline automation for npm & website releases
  • Drop pipeline support for Node.js 8.x due to its EOL end of 2019

Removed

  • Travis pipeline, relying solely on GitHub Actions

[2.0.0-alpha.2] - 2019-12-14

Added

  • Introduced DockestServices, servicing as the interface for users to specify which services to spin up during testing
  • Introduced custom healthcheck that can be passed along with the DockestServices

Removed

  • Removed typedoc
  • Removed attachRunners

Changed

  • Moved concept of Runners from a public interface to an internal one

[2.0.0-alpha.1] - 2019-10-24

Added

  • Introduced monorepo structure using yarn workspaces and lerna
  • Use compose CLI for merging compose files #82
  • Allow containers to connect to host machine/dockest inside container support #91

Changed

  • Broke out runners from Dockest constructor and introduced attachRunners method
  • Started moving towards relying heavier on compose files rather than supplying runners that'll generate compose files

[1.0.3] - 2019-08-30

Added

  • New logo 🎉 #69
  • Supports services being referenced as a dependency multiple times #68

Fixed

  • Jest commands work when including projects #66 #53

Changed

  • Bumped all dependencies #67

[1.0.2] - 2019-08-20

Added

  • SimpleRunner #63
  • Support for parsing of Compose Files as well as supplying runners with individual images. #55
  • Support for parallelism of Jest and healthchecking Runner. #55

[1.0.0] - 2019-07-22

Added

  • KafkaRunner #55
  • ZooKeeperRunner #55
  • PostgresRunner
  • RedisRunner #40

Changed

  • Improvements to Dockest's interface (breaking change)
  • Improved logging structure
  • Improved test coverage
  • Added typedoc for automatic documentation generation
  • Migrated from docker-compose run to docker-compose up due to limitation in network accessibility between services (I'm looking at you Kafka & ZooKeeper)