DEV Community

Nick Ball
Nick Ball

Posted on • Originally published at npbee.me on

Testing an API endpoint with Deno

Deno has become one of my favorite tools for web development. Its refreshing to have all of the essential features readily available without needing to reach for third-party libraries. Recently, Ive been working on an API-first version of Waveformr and landed on a way to test the endpoints that Id like to share.

The endpoint

For the purposes of this article, Im going to set up a basic HTTP GET endpoint using Deno and Hono. But note, Deno and Hono here are implementation details. Im only using them because they are easy to set up, but the tests wont care what were using to implement the endpoint.

The endpoint implementation will look like this:

import { Hono } from "https://deno.land/x/hono/mod.ts";

const app = new Hono();

// The "database"
const books = new Map();

// Post a book with a name to create it
app.post("/book", async (c) => {
  const body = await c.req.json<{ name: string }>();
  const { name } = body;
  const book = {
    id: crypto.randomUUID(),
    name,
  };

  books.set(book.id, book);
  return c.json({ status: "ok", id: book.id });
});

// Look up a book by id
app.get("/book/:id", (c) => {
  const book = books.get(c.req.param("id"));
  if (book) {
    return c.json({ status: "ok", book });
  } else {
    return c.json({ status: "error", message: "not found" }, 404);
  }
});

export function run() {
  return Deno.serve(app.fetch);
}

// Run this immediately if this is the entrypoint to the program
// Useful for testing because we can import this file and run it
// with a function call when we're ready
if (import.meta.main) {
  run();
}

Enter fullscreen mode Exit fullscreen mode

The test

The test code looks like this:

import { assert } from "https://deno.land/std@0.202.0/assert/assert.ts";
import { run } from "./main.ts";
import { assertEquals } from "https://deno.land/std@0.202.0/assert/mod.ts";

const PORT = 8000;
const url = (path: string) => new URL(`http://localhost:${PORT}/${path}`);

Deno.test("POST a new book", async (t) => {
  // Pass an `AbortController` so that we can abort the server when we're done
  const controller = new AbortController();
  run({
    port: PORT,
    signal: controller.signal,
  });

  const resp = await fetch(url("book"), {
    method: "POST",
    body: JSON.stringify({
      name: "The Great Gatsby",
    }),
  });

  assertEquals(resp.status, 200);

  const { id, status } = await resp.json();
  assertEquals(status, "ok");
  assert(typeof id === "string");

  const newBookResp = await fetch(url(`book/${id}`));
  assertEquals(newBookResp.status, 200);
  const newBookJson = await newBookResp.json();
  assertEquals(newBookJson.book.name, "The Great Gatsby");

  controller.abort();
});

Enter fullscreen mode Exit fullscreen mode

Running the tests will give an output that looks a bit like this:

Check file:///Users/nickball/code/deno-endpoint-testing/main_test.ts
running 1 test from ./main_test.ts
POST a new book ...
------- output -------
Listening on http://localhost:8000/
----- output end -----
POST a new book ... ok (8ms)

ok | 1 passed | 0 failed (26ms)

Enter fullscreen mode Exit fullscreen mode

What I like about this method is that were hitting the endpoint like a real user would and using standard Web APIs. Deno has this all built in, so it feels very natural. One note here is that were passing an AbortController into the server so that we can manually kill the server after were done testing it.

Thats it!

Top comments (0)