Skip to main content
On this page

Test sanitizers

The deno test runner offers several sanitizers that catch tests misbehaving in ways assertions don't see: leaking async operations, unclosed resources, and unexpected process exits.

Resource sanitizer Jump to heading

The resource sanitizer ensures that all I/O resources created during a test are closed, to prevent leaks.

I/O resources are things like Deno.FsFile handles, network connections, fetch bodies, timers, and other resources that are not automatically garbage collected.

You should always close resources when you are done with them. For example, to close a file:

const file = await Deno.open("hello.txt");
// Do something with the file
file.close(); // <- Always close the file when you are done with it

To close a network connection:

const conn = await Deno.connect({ hostname: "example.com", port: 80 });
// Do something with the connection
conn.close(); // <- Always close the connection when you are done with it

To close a fetch body:

const response = await fetch("https://example.com");
// Do something with the response
await response.body?.cancel(); // <- Always cancel the body when you are done with it, if you didn't consume it otherwise

As of Deno 2.8 this sanitizer is off by default. Opt in with sanitizeResources: true, or with one of the global mechanisms described in Enabling sanitizers globally.

Deno.test({
  name: "no leaks allowed",
  async fn() {
    using file = await Deno.open("hello.txt");
    // ...
  },
  sanitizeResources: true,
});

Async operation sanitizer Jump to heading

The async operation sanitizer ensures that all async operations started in a test are completed before the test ends. This is important because if an async operation is not awaited, the test will end before the operation is completed, and the test will be marked as successful even if the operation may have actually failed.

You should always await all async operations in your tests. For example:

Deno.test({
  name: "async operation test",
  async fn() {
    await new Promise((resolve) => setTimeout(resolve, 1000));
  },
});

As of Deno 2.8 this sanitizer is off by default. Opt in with sanitizeOps: true, or with one of the global mechanisms described below.

Deno.test({
  name: "no leaked ops allowed",
  async fn() {
    await someAsyncWork();
  },
  sanitizeOps: true,
});

Enabling sanitizers globally Jump to heading

If you want the pre-2.8 behavior (resource and op sanitizers on for every test), you can re-enable them at any of five scopes. Higher-precedence settings override lower ones.

  1. Per-test (highest precedence):

    Deno.test({
      name: "strict",
      sanitizeOps: true,
      sanitizeResources: true,
      fn() {/* … */},
    });
    
  2. Per-module with Deno.test.sanitizer():

    Deno.test.sanitizer({ ops: true, resources: true });
    
    Deno.test("uses module-level sanitizers", () => {/* … */});
    
  3. CLI flags: --sanitize-ops and --sanitize-resources.

  4. Environment variables: DENO_TEST_SANITIZE_OPS=1 and DENO_TEST_SANITIZE_RESOURCES=1.

  5. deno.json (lowest precedence):

    {
      "test": {
        "sanitizeOps": true,
        "sanitizeResources": true
      }
    }
    

Exit sanitizer Jump to heading

The exit sanitizer ensures that tested code doesn’t call Deno.exit(), which could signal a false test success.

This sanitizer is enabled by default, but can be disabled with sanitizeExit: false.

Deno.test({
  name: "false success",
  fn() {
    Deno.exit(0);
  },
  sanitizeExit: false,
});

// This test never runs, because the process exits during "false success" test
Deno.test({
  name: "failing test",
  fn() {
    throw new Error("this test fails");
  },
});

Did you find what you needed?

Edit this page
Privacy policy