Code coverage for a running Deno server
Tags: deno, coverage, node, testing
Published: 2022-10-27 01:12:57

Deno (a modern Rust based TypeScript runtime) comes with code coverage measurement built in. Unfortunately, [right](https://github.com/denoland/deno/discussions/16292) [now](https://github.com/denoland/deno/issues/16440), its only supported for `deno test` but not for `deno run`.

This seems unnecessarily limiting, since it makes measuring coverage in a running Deno server by running API tests against it convoluted - but not impossible. This blog outlines a workaround that lets you achieve just this. Thanks to an idea from [zachauten](https://github.com/denoland/deno/discussions/16292#discussioncomment-3945897).

Let's say you had a bunch of Postman API tests written up for testing a Deno HTTP REST API server that you execute using the excellent [newman](https://learning.postman.com/docs/running-collections/using-newman-cli/command-line-integration-with-newman/) CLI collection runner. Further, lets say you wanted to measure code coverage from running these API tests.

You would think you would be able to do something like this:

```bash
# Start Deno HTTP server and put it in background
deno run --coverage ./main.ts &
export PID=$!

# Run Postman API tests using newman
npx newman run ./api-tests.postman.json  

# Kill server to generate coverage
kill $PID
```

Unfortunately, the above does not work because `deno run --coverage` is not supported.

Instead, the trick is to do all these steps (start server, run API tests, stop server) inside a Deno **test** and then use `deno test --coverage`. Sigh 😞. Seems labyrinthine but here is how to do it.

## Step 1: Make your server a testable module

Make sure that your server is in a form that can be consumed from a Deno test and started and stopped there. For example a simple server can look like:

```ts
// server.ts
export function requestHandler(_req: Request): Promise<Response> {
  return Promise.resolve(new Response("Hello, world!"));
}
```

This server can also be consumed from your main entry point thus:

```ts
// main.ts
import { serve } from "std/http/server.ts";
import { requestHandler } from "./server.ts";

serve(requestHandler);
```

## Step 2: Write a test that calls the server

Now write a test that does the following:

1. Start server with abort controller
1. Shell out to run `newman` API tests
1. Stop server using abort controller

For example:

```ts
// server_test.ts
import { assert } from "std/testing/asserts.ts";
import { serve } from "std/http/server.ts";
import { requestHandler } from "./server.ts";

Deno.test("API Tests", async () => {
  // Start server (with abort controller)
  const controller = new AbortController();
  const { signal } = controller;
  const server = serve(requestHandler, { signal });

  // Run API tests
  const p = Deno.run({
    cmd: ["npx", "newman", "run", "./api-tests.postman.json"],
  });
  const { success } = await p.status();
  p.close();
  assert(success);

  // Stop server (using abort controller)
  controller.abort();

  // Wait for server to be closed
  await server;
});

```

## Step 3: Run test in coverage mode

Finally, we can run the server test with the `--coverage` flag.

```bash
deno test --allow-net --allow-run --coverage=coverage ./server_test.ts
```

This will run the outer `server_test.ts` in coverage mode, generating code coverage in the `coverage` folder which can be further processed. See help on [Deno coverage](https://deno.land/manual/testing/coverage).

## Conclusion

Hopefully, in the long run, Deno will introduce a `deno run --coverage` option. Till then, this workaround will have to do.

For a more fleshed out version of the code in this post, please see:

{% github anishkny/deno-api-tests-coverage %}