0.3.0 • Published 3 years ago

calligrapho v0.3.0

Weekly downloads
-
License
MIT
Repository
-
Last release
3 years ago

Calligrapho - Contract testing server mock

Calligrapho is a library and command line tool that helps you emulate an API server.

When it comes to testing, end-to-end tests are the hardest to write and maintain, while also being the most expensive in terms of resources and execution time. However, E2E are the only tests that can fill us with confidence about the application behaviour, as they closely mimic the real behaviour of the application and of its relationships.

A common scenario to test E2E is a provider-consumer relationship. The consumer is an application that receives a message (HTTP request, queued message, etc) while the producer is the application that sends the message and possibly expects an answer. Tests modelling provider-consumer relationships are either incredibly complex (E2E spectrum) or extensively mocked (Unit spectrum). Contract testing is our middle ground. In contract tests we only tests the data contract between a producer and a consumer, and see whether it still holds.

Calligrapho wants to help you write contract tests. At its core, it's a configurable server. You define a calligrapho.config.ts (or calligrapho.config.js) configuration file and Calligrapho will start full Koa application ready to be used. The benefit here is that you can re-use logic from your own producer service, such as validators, authenticators or random values generators.

But the true strenght of Calligrapho is the ability of generating a calligrapho.config.ts file by querying your consumer service (WIP). While in query mode, Calligrapho will accept an array of AxiosRequestConfig objects exported in a calligrapho.query.ts or calligrapho.query.js file. It will then perform HTTP requests and record the responses, generating a full calligrapho configuration file. Since the responses from the consumer service will be real, your tests against Calligrapho will take the best from unit tests and E2E tests:

From unit tests, you will get the easy setup and performance. Your tests against a Koa application that just returns the values you specified will give you responses in the millisecond mark. Additionally, since no real request will be performed, you will not need to worry about setup and teardown.

From E2E tests, you will get the confidence of using a real set of data against a recently up-to-date service that mimics your consumer application. You will also get a real set of data that is related to your original message.

Contract tests with Calligrapho will allow you to run tests that are close in quality and confidence to E2E at the speed and ease of Unit Tests, while developing. And when you run the tests in the pipeline, you can just query with Calligrapho to generate the latest version of your consumer application responses, de facto transforming your contract tests into full E2E tests.

Installation

# Yarn
yarn add --dev calligrapho

# NPM
npm install --dev calligrapho

You are all set!

Usage

Calligrapho Configuration

You can import the CalligraphoConfiguration interface exposes in calligrapho/types. All keys are optional, but a configuration without a routes object will be quite pointless as the Koa application will only answer with a prefigured "Hello World" route.

/**
 * Calligrapho runtime configuration
 */
export interface CalligraphoConfiguration {
  /**
   * Defines the list of routes to be used by Calligrapho.
   * See `CalligraphoRoute` for details
   */
  routes: CalligraphoRoute<unknown>[];

  /** Koa binding port number */
  port: number;

  /** Koa binding host */
  host: string;

  /** HTTP protocol to be used */
  protocol: 'http' | 'https';
}
/**
 * Represents a CalligraphoRoute
 */
export interface CalligraphoRoute<Output, User = number> {
  /**
   * HTTP path excluding the base url.
   * Defaults to /
   */
  path: string;

  /**
   * HTTP Method.
   * Defaults to GET
   */
  method: CalligraphoSupportedMethod;

  /**
   * Authentication function.
   * Returns a User object or null for anonymous users.
   * Defaults to null.
   */
  auth: (context: Context) => Promise<User | null>;

  /**
   * Validator function.
   * Returns a Promise wrapping any object.
   * Any truthy value will automatically fail the request
   * with HTTP 400, returning the truthy value.
   * Defaults to a function resolving null.
   */
  validator: (values: unknown) => Promise<unknown>;

  /**
   * Generator function.
   * Allows you to specify the status code
   * and the http response body you expect
   * from the route.
   *
   * See CalligraphoContext for details.
   *
   * Defaults to a function returning HTTP 200
   */
  generator: (context: CalligraphoContext<User>) => CalligraphoResponse<Output>;

  /**
   * It represents the starting state of your application.
   * Think of it as the database table associated with the route.
   * For a route handling authentication, it may be helpful
   * defining a list of users here.
   *
   * Defaults to a function returning an empty array.
   */
  startingData: () => Output[],
}
/**
 * Represents the context provided
 * to Generators functions.
 */
export type CalligraphoContext<User = number> = {
  /**
   * A map associating routes to their "database table".
   * Use to CRUD in-memory data.
   */
  db: CalligraphoDB;

  /**
   * Authentication function outcome.
   */
  user: User | null;

  /**
   * Koa.Context including
   * request and response objects.
   */
  context: Context;
};

Programmatic use

You can import the Calligrapho class from calligrapho/server and create an instance yourself. This gives you flexibility should you prefer to run Calligrapho directly in your tests suite.

The Calligrapho class exposes a Calligrapho.events object that subclasses EventEmitter, so you can emit and listen to the following events:

  • calligrapho:start: Triggers calligrapho.start()
  • calligrapho:started: Emitted at the end of calligrapho.start()
  • calligrapho:request: Emitted at the beginning of calligrapho.request()
  • calligrapho:response: Emitted at the end of calligrapho.request()
  • calligrapho:close: Triggers calligrapho.close()
  • calligrapho:closed: Emitted at the end of calligrapho.close()

Listening to events can be helpful while embeding Calligrapho in your test suite, as you will be able to wait for events in your setup and teardown functions.

For a few examples of programmatic use of Calligrapho in a test suite, see some of Calligrapho's functional tests.