Docs

Write tests

Write a contract test

A contract test runs in Node under Vitest. Testkit starts your service, gives the test an HTTP client that points at it, and stops the service when the run ends.

View as Markdown

On this page
  1. Before you start
  2. Write the test
  3. Run it
  4. Read a failure
  5. What a scenario receives
  6. Use Vitest where you need it

Before you start

You need one service in testkit.config.ts. Get started adds it in step two.

Write the test

Create the file in a __testkit__ directory beside the code it tests.

src/checkout/__testkit__/refund.contract.testkit.ts
import { defineFile } from "@gfdelarue/testkit";
import { contractScenario, expect } from "@gfdelarue/testkit/node-vitest/contract";

export const testkit = defineFile({ engine: "node-vitest" });

contractScenario("a refund pays out once", async ({ http }) => {
	const first = await http().post("/orders/8841/refunds");
	const second = await http().post("/orders/8841/refunds");
	expect(first.status).toBe(201);
	expect(second.status).toBe(409);
});

contractScenario registers one case. Name the case after the behaviour you expect, so the name is true when the product is right.

Run it

$npx testkit run contract
[1/2] PASS api contract src/checkout/__testkit__/refund.contract.testkit.ts 1s
[2/2] PASS api contract src/__testkit__/health.contract.testkit.ts 1s

Summary
Result: PASSED
Passed: 2
Failed: 0
Skipped: 0
Not run: 0
Files: 2
Duration: 8s
Run ID: 1791890142106-1343177-e5c04590

To run one file, pass --file src/checkout/__testkit__/refund.contract.testkit.ts.

Read a failure

When the service pays the refund twice, the second assertion fails. The run prints the assertion and the line that made it, then the rest of the stack.

$npx testkit run contract
[1/2] FAIL api contract src/checkout/__testkit__/refund.contract.testkit.ts 1s
    AssertionError: expected 201 to be 409 // Object.is equality at
    /repo/src/checkout/__testkit__/refund.contract.testkit.ts:10:24
[2/2] PASS api contract src/__testkit__/health.contract.testkit.ts 1s

Summary
Result: FAILED
Passed: 1
Failed: 1
Skipped: 0
Not run: 0
Files: 2
Duration: 10s
Run ID: 1791890523760-1353387-5113dd97

The command exits 1. Leave the test failing until the product is fixed. A test that fails for a real bug is the evidence a bug report needs.

What a scenario receives

FieldWhat it is
http(options?)Returns an HTTP client for the service. options takes baseUrl and defaultHeaders.
baseUrlThe URL of the running service
envThe service's declared environment values, read-only
fixture(name)A fixture file, with text(), json(), buffer(), sha256, and sizeBytes

The client has get, post, put, patch, delete, and request(method, path, body?, headers?). get takes a path and optional headers. The others take a path, an optional body, and optional headers.

Every response has the same fields.

FieldType
statusnumber
headersRecord<string, string>
bodystring
json()Parses body
method, path, urlstring

Use Vitest where you need it

expect is Vitest's expect. A contract file is a native Vitest suite, so test.skip(...) and test.todo(...) from vitest work beside contractScenario.

To give one scenario its own time limit, pass options as the second argument.

contractScenario("the export finishes", { timeoutMs: 20_000 }, async ({ http }) => {
	const response = await http().post("/exports");
	expect(response.status).toBe(202);
});
  1. OverviewStartWhat Testkit is, and what you need to run it.
  2. Get startedStartFive steps from install to a merge gate.
  3. Test filesWrite testsWhere a test lives and what its name means.
  4. Contract testsWrite testsCall your running service from Node.
  5. HTTP testsWrite testsDrive routes with k6, signed in as named actors.
  6. Browser testsWrite testsAssert on a real page in Chromium.
  7. Database testsWrite testsRun SQL against a Postgres that Testkit builds.
  8. CIRunOne command decides whether a pull request merges.
  9. ResultsRunWhere a run's evidence lands and how to read it.
  10. CLIRunEvery command and flag.
  11. ConfigurationRunEvery field of testkit.config.ts.
  12. CampaignsHunt bugsGive your agent a target and collect proven bugs.
  13. Bug reportsHunt bugsThe five things every issue states.
  14. TurnstoneTurnstoneWhat the dashboard keeps, and what it never stores.
  15. Connect GitHubTurnstoneConnect a repository, and see the access Turnstone asks for.
  16. Connect GiteaTurnstoneAdd your Gitea host, then connect a repository.
  17. SyncTurnstoneWhen Turnstone reads your issues, and what it reads.
  18. FactsTurnstoneEach fact about a bug, and where Turnstone reads it.