Testing

    Firstly, let’s create a file and register a test case using Deno.test() function.

    Secondly, run the test using deno test subcommand.

    1. $ deno test url_test.ts
    2. running 1 test from file:///dev/url_test.js
    3. test url test ... ok (2ms)
    4. test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (9ms)

    Writing tests

    To define a test you need to register it with a call to Deno.test API. There are multiple overloads of this API to allow for greatest flexibility and easy switching between the forms (eg. when you need to quickly focus a single test for debugging, using only: true option):

    1. import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
    2. // Compact form: name and function
    3. Deno.test("hello world #1", () => {
    4. const x = 1 + 2;
    5. assertEquals(x, 3);
    6. });
    7. // Compact form: named function.
    8. Deno.test(function helloWorld3() {
    9. const x = 1 + 2;
    10. assertEquals(x, 3);
    11. });
    12. // Longer form: test definition.
    13. Deno.test({
    14. name: "hello world #2",
    15. fn: () => {
    16. const x = 1 + 2;
    17. assertEquals(x, 3);
    18. },
    19. });
    20. // Similar to compact form, with additional configuration as a second argument.
    21. Deno.test("hello world #4", { permissions: { read: true } }, () => {
    22. const x = 1 + 2;
    23. assertEquals(x, 3);
    24. });
    25. // Similar to longer form, with test function as a second argument.
    26. Deno.test(
    27. { name: "hello world #5", permissions: { read: true } },
    28. () => {
    29. const x = 1 + 2;
    30. assertEquals(x, 3);
    31. },
    32. );
    33. // Similar to longer form, with a named test function as a second argument.
    34. Deno.test({ permissions: { read: true } }, function helloWorld6() {
    35. const x = 1 + 2;
    36. assertEquals(x, 3);
    37. });

    You can also test asynchronous code by passing a test function that returns a promise. For this you can use the async keyword when defining a function:

    1. import { delay } from "https://deno.land/std@$STD_VERSION/async/delay.ts";
    2. Deno.test("async hello world", async () => {
    3. const x = 1 + 2;
    4. // await some async task
    5. await delay(100);
    6. if (x !== 3) {
    7. throw Error("x should be equal to 3");
    8. }
    9. });

    The test steps API provides a way to report distinct steps within a test and do setup and teardown code within that test.

    1. Deno.test("database", async (t) => {
    2. const db = await Database.connect("postgres://localhost/test");
    3. await t.step("insert user", async () => {
    4. const users = await db.query(
    5. "INSERT INTO users (name) VALUES ('Deno') RETURNING *",
    6. );
    7. assertEquals(users.length, 1);
    8. assertEquals(users[0].name, "Deno");
    9. });
    10. // or provide a test definition
    11. await t.step({
    12. name: "insert book",
    13. fn: async () => {
    14. "INSERT INTO books (name) VALUES ('The Deno Manual') RETURNING *",
    15. );
    16. assertEquals(books.length, 1);
    17. assertEquals(books[0].name, "The Deno Manual");
    18. },
    19. ignore: false,
    20. // these default to the parent test or step's value
    21. sanitizeOps: true,
    22. sanitizeResources: true,
    23. sanitizeExit: true,
    24. });
    25. // nested steps are also supported
    26. await t.step("update and delete", async (t) => {
    27. await t.step("update", () => {
    28. // even though this test throws, the outer promise does not reject
    29. // and the next test step will run
    30. throw new Error("Fail.");
    31. });
    32. await t.step("delete", () => {
    33. // ...etc...
    34. });
    35. });
    36. // steps return a value saying if they ran or not
    37. const testRan = await t.step({
    38. name: "copy books",
    39. fn: () => {
    40. // ...etc...
    41. },
    42. ignore: true, // was ignored, so will return `false`
    43. });
    44. // steps can be run concurrently if sanitizers are disabled on sibling steps
    45. const testCases = [1, 2, 3];
    46. await Promise.all(testCases.map((testCase) =>
    47. t.step({
    48. name: `case ${testCase}`,
    49. fn: async () => {
    50. // ...etc...
    51. },
    52. sanitizeOps: false,
    53. sanitizeResources: false,
    54. sanitizeExit: false,
    55. })
    56. ));
    57. db.close();
    58. });

    Outputs:

    1. test database ...
    2. test insert user ... ok (2ms)
    3. test insert book ... ok (14ms)
    4. test update and delete ...
    5. test update ... FAILED (17ms)
    6. Error: Fail.
    7. at <stack trace omitted>
    8. test delete ... ok (19ms)
    9. FAILED (46ms)
    10. test copy books ... ignored (0ms)
    11. test case 1 ... ok (14ms)
    12. test case 2 ... ok (14ms)
    13. test case 3 ... ok (14ms)
    14. FAILED (111ms)

    Notes:

    1. Test steps cannot be run concurrently unless sanitizers on a sibling step or parent test are disabled.
    2. If nesting steps, ensure you specify a parameter for the parent step.
      1. Deno.test("my test", (t) => {
      2. await t.step("step", async (t) => {
      3. // note the `t` used here is for the parent step and not the outer `Deno.test`
      4. await t.step("sub-step", () => {
      5. });
      6. });
      7. });

    Nested test steps

    To run the test, call deno test with the file that contains your test function. You can also omit the file name, in which case all tests in the current directory (recursively) that match the glob {*_,*.,}test.{ts, tsx, mts, js, mjs, jsx, cjs, cts} will be run. If you pass a directory, all files in the directory that match this glob will be run.

    The glob expands to:

    • files named test.{ts, tsx, mts, js, mjs, jsx, cjs, cts},
    • or files ending with .test.{ts, tsx, mts, js, mjs, jsx, cjs, cts},
    • or files ending with _test.{ts, tsx, mts, js, mjs, jsx, cjs, cts}
    1. # Pass additional arguments to the test file
    2. deno test my_test.ts -- -e --foo --bar

    deno test uses the same permission model as deno run and therefore will require, for example, to write to the file system during testing.

    To see all runtime options with deno test, you can reference the command line help:

    1. deno help test

    Filtering

    There are a number of options to filter the tests you are running.

    Tests can be run individually or in groups using the command line --filter option.

    The filter flags accept a string or a pattern as value.

    Assuming the following tests:

    1. Deno.test({ name: "my-test", fn: myTest });
    2. Deno.test({ name: "test-1", fn: test1 });
    3. Deno.test({ name: "test2", fn: test2 });

    This command will run all of these tests because they all contain the word “test”.

    1. deno test --filter "test" tests/

    On the flip side, the following command uses a pattern and will run the second and third tests.

    1. deno test --filter "/test-*\d/" tests/

    To let Deno know that you want to use a pattern, wrap your filter with forward-slashes like the JavaScript syntactic sugar for a REGEX.

    Filtering out (Ignoring these tests)

    Sometimes you want to ignore tests based on some sort of condition (for example you only want a test to run on Windows). For this you can use the ignore boolean in the test definition. If it is set to true the test will be skipped.

    1. Deno.test({
    2. name: "do macOS feature",
    3. ignore: Deno.build.os !== "darwin",
    4. fn() {
    5. doMacOSFeature();
    6. },
    7. });

    Filtering in (Only run these tests)

    Sometimes you may be in the middle of a problem within a large test class and you would like to focus on just that test and ignore the rest for now. For this you can use the only option to tell the test framework to only run tests with this set to true. Multiple tests can set this option. While the test run will report on the success or failure of each test, the overall test run will always fail if any test is flagged with only, as this is a temporary measure only which disables nearly all of your tests.

    If you have a long-running test suite and wish for it to stop on the first failure, you can specify the --fail-fast flag when running the suite.

    1. deno test --fail-fast

    Integration with testing libraries

    Deno’s test runner works with popular testing libraries like Chai, or fast-check.

    For example integration see:

    Test spies are function stand-ins that are used to assert if a function’s internal behavior matches expectations. Sinon is a widely used testing library that provides test spies and can be used in Deno by importing it from a CDN, such as Skypack:

    1. import sinon from "https://cdn.skypack.dev/sinon";

    Say we have two functions, foo and bar and want to assert that bar is called during execution of foo. There are a few ways to achieve this with Sinon, one is to have function foo take another function as a parameter:

    1. // my_file.js
    2. export function bar() {/*...*/}
    3. export function foo(fn) {
    4. fn();
    5. }

    This way, we can call foo(bar) in the application code or wrap a spy function around bar and call foo(spy) in the testing code:

    1. import sinon from "https://cdn.skypack.dev/sinon";
    2. import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
    3. import { bar, foo } from "./my_file.js";
    4. Deno.test("calls bar during execution of foo", () => {
    5. // create a test spy that wraps 'bar'
    6. const spy = sinon.spy(bar);
    7. // call function 'foo' and pass the spy as an argument
    8. foo(spy);
    9. assertEquals(spy.called, true);
    10. assertEquals(spy.getCalls().length, 1);
    11. });

    If you prefer not to add additional parameters for testing purposes only, you can also use sinon to wrap a method on an object instead. In other JavaScript environments bar might have been accessible via a global such as window and callable via sinon.spy(window, "bar"), but in Deno this will not work and instead you can export an object with the functions to be tested. This means rewriting my_file.js to something like this:

    1. // my_file.js
    2. function bar() {/*...*/}
    3. export const funcs = {
    4. bar,
    5. };
    6. // 'foo' no longer takes a parameter, but calls 'bar' from an object
    7. export function foo() {
    8. funcs.bar();
    9. }
    1. import sinon from "https://cdn.skypack.dev/sinon";
    2. import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
    3. import { foo, funcs } from "./my_file.js";
    4. Deno.test("calls bar during execution of foo", () => {
    5. // create a test spy that wraps 'bar' on the 'funcs' object
    6. const spy = sinon.spy(funcs, "bar");
    7. // call function 'foo' without an argument
    8. foo();
    9. assertEquals(spy.called, true);
    10. assertEquals(spy.getCalls().length, 1);
    11. });