Avoid leaking resources in Deno tests
Tags: deno, oak, fetch
Published: 2020-05-30 15:10:48

---
title: Avoid leaking resources in Deno tests
published: true
description: 
tags: #deno, #oak, #fetch
---

Recently I have been using [Deno](https://deno.land/) to implement a simple web API to play with the new TypeScript runtime. In order to test the APIs, today I wrote a test, which is similar to the following:

```typescript
import {
  assertStrictEq,
} from "https://deno.land/std@v0.51.0/testing/asserts.ts";

import {
  Application,
} from "https://deno.land/x/oak@v5.0.0/mod.ts";

Deno.test("server with a simple middleware", async () => {
  const app = new Application();
  const controller  = new AbortController();
  const { signal } = controller;

  app.use(async (ctx) => {
    ctx.response.body = 'Hello world.';
  });

  const listenPromise = app.listen({ port: 8000, signal });

  const response = await fetch("http://127.0.0.1:8000/");
  assertStrictEq(response.ok, true);

  controller.abort();

  await listenPromise;
});
```

The test is simple:

* Create a new application.
* Mount a middleware on the application so that the application will respond with "Hello world." when a request arrives.
* Start the application and listen on the specified port (i.e. `8000`).
* Send a request to the application and test whether the response status code is in expected range (`response.ok` means the status is in the range 200 - 299).
* Stop the application.

This test should pass... Except when it didn't:

```
> deno test --allow-net
running 1 tests
test server with a simple middleware ... FAILED (38ms)

failures:

server with a simple middleware
AssertionError: Test case is leaking resources.
Before: {
  "0": "stdin",
  "1": "stdout",
  "2": "stderr"
}
After: {
  "0": "stdin",
  "1": "stdout",
  "2": "stderr",
  "5": "httpBody"
}

Make sure to close all open resource handles returned from Deno APIs before
finishing test case.
    at Object.assert ($deno$/util.ts:33:11)
    at Object.resourceSanitizer [as fn] ($deno$/testing.ts:81:5)
    at async TestApi.[Symbol.asyncIterator] ($deno$/testing.ts:264:11)
    at async Object.runTests ($deno$/testing.ts:346:20)

failures:

        server with a simple middleware

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (42ms)
```

At first I didn't understand which part of the test was leaking resources. After some experiments, I found that the root cause was that I didn't read the response body obtained from the `fetch()` function call.

After calling `await fetch()` to obtain the response, one can call `response.text()` (or `response.json()`) to read the response stream and parse it as JSON data (or plain text) (see the [document](https://developer.mozilla.org/en-US/docs/Web/API/Body#Methods) on MDN for details). Without reading the response stream (which is expected to be read, *I guess*), Deno would think the test is leaking resources.

It is very simple to fix the problem. When using Deno 1.0.3 (the latest version at the time of writing), one can call `cancel()` on the response stream to say that the data is not needed anymore. I updated the test:

```typescript
import {
  assertStrictEq,
} from "https://deno.land/std@v0.51.0/testing/asserts.ts";

import {
  Application,
} from "https://deno.land/x/oak@v5.0.0/mod.ts";

Deno.test("server with a simple middleware", async () => {
  const app = new Application();
  const controller  = new AbortController();
  const { signal } = controller;

  app.use(async (ctx) => {
    ctx.response.body = 'Hello world.';
  });

  const listenPromise = app.listen({ port: 8000, signal });

  const response = await fetch("http://127.0.0.1:8000/");
  assertStrictEq(response.ok, true);
  // The response body is not needed.
  await response.body?.cancel();

  controller.abort();

  await listenPromise;
});
```

Notice that line after checking the response status:

```typescript
await response.body?.cancel();
```

(See [Optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) if you wonder what does `?.` mean.)

Finally the test passed:

```
> deno test --allow-net
running 1 tests
test server with a simple middleware ... ok (89ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (93ms)
```

If you are using Deno 1.0.2 (or previous versions), the `cancel()` function is not implemented:

```
> deno test --allow-net
running 1 tests
test server with a simple middleware ... FAILED (71ms)

failures:

server with a simple middleware
Error: not implemented
    at Object.notImplemented ($deno$/util.ts:64:9)
    at Body.cancel ($deno$/web/fetch.ts:234:12)
```

You can still read the response body and throw it away immediately:

```typescript
await response.text();
```

In fact, the same leaking resources problem has been asked and addressed around a month ago, you can see the issue [#4735](https://github.com/denoland/deno/issues/4735) on Deno's code repository for more details.

Some useful links:

* [https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/cancel](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/cancel)
* [https://streams.spec.whatwg.org/#rs-cancel](https://streams.spec.whatwg.org/#rs-cancel)