Deno Fresh Testing: End-to-end Site Tests
Tags: deno, testing, webdev
Published: 2023-07-31 12:55:48

---
title: "Deno Fresh Testing: End-to-end Site Tests"
published: "true"
description: "Deno Fresh Testing ☑️ how you can add end-to-end testing for HTML pages content and API routes to your Deno 🍋 Fresh app."
tags: "deno, testing, webdev"
canonical_url: "https://rodneylab.com/deno-fresh-testing/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0zwseyofev8gndyiruf.png"
---

## 📝 Deno Fresh Testing

In this post, we see how you can set up Deno Fresh Testing on your Fresh app.  We focus on end-to-end testing, though you can use the APIs seen here for unit testing utility functions in your project, for example.

**Unit testing** typically shines a spotlight on a single function or component; that is, to check that it behaves as expected.  Those tests are performed in isolation, and integration tests might be added to check for side effects that arising from combining components.  You will combine components in a way they will be used in production for **integration tests**, though end-to-end tests are an ideal way to check everything works as expected.  **End-to-end testing** is applied at a higher level, interacting with the app as the end user would.  That applies both to HTML page contents and API routes.  Ideally, end-to-end test design makes no assumptions on the actual implementation used.

We focus on that last kind of test here, though there is an outline of <a href="https://rodneylab.com/deno-fresh-getting-started/">running Deno unit tests in the post on Getting Started with Deno Fresh</a>.

## 🧱 What are we Building?

It makes most sense to design tests for an app you are working on, rather than building out an app just for the sake of seeing tests.  For that reason, we focus on adding tests to an existing Deno project.  This might be a nascent project, if you prefer to follow the test-driven development  (**TDD**) approach.

![Deno Fresh Testing: Screen capture shows output from running Deno test in the Terminal, with all tests (one test on the Jokes A P I route and all three on the hime route) passing.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/untah2ybcd8zp4avqtjc.png)

We add tests to a very basic Deno Fresh app, which should help get started with adding tests to your own app.  The full code is available in a GitHub repo (link further down).

Hopefully you can benefit from the post independently of which testing philosophy you prefer.  Let&rsquo;s get started!

## ⚙️ Project Config

Deno has a built-in test runner, which you can call with the `deno test` command.  This looks for test files within your project (we use the `something_test.ts` naming pattern in this post, though Deno will also automatically pick up `something.test.ts` files).  If you prefer another pattern, <a href="https://deno.land/manual@v1.35.0/getting_started/configuration_file#full-example">`deno.json` accepts `test.include`, as well as `test.exclude`, letting you customize the test search paths</a>.  The Deno Standard Library provides the testing assertions which we need.  For DOM testing we add `deno_dom` here to write queries on returned HTML.

Update `deno.json` adding a test script, as well as the `deno-dom` import:

```json
{
  "lock": false,
  "tasks": {
    "start": "deno run -A --watch=static/,routes/ dev.ts",
    "test": "deno test --allow-env --allow-read --allow-net",
    "update": "deno run -A -r https://fresh.deno.dev/update ."
  },
  "imports": {
    "@/": "./",
    "$fresh/": "https://deno.land/x/fresh@1.3.1/",
    "deno-dom/": "https://deno.land/x/deno_dom@v0.1.38/",
    "preact": "https://esm.sh/preact@10.15.1",
    "preact/": "https://esm.sh/preact@10.15.1/",
    "preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.0",
    "@preact/signals": "https://esm.sh/*@preact/signals@1.1.3",
    "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3",
    "$std/": "https://deno.land/std@0.196.0/"
  },
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}
```

### HTML Lang Attribute

If your site is not written in English (as spoken in the US), you will want to include an HTML lang attribute on generated HTML pages.  In Deno Fresh, this is done by providing the language in the `start` method options in `main.ts`:

```typescript
import { start } from "$fresh/server.ts";
import "$std/dotenv/load.ts";
import { startOptions } from "@/configuration/configuration.ts";
import manifest from "@/fresh.gen.ts";

await start(manifest, startOptions);
```

We could write the start options straight into that file.  However, we would need to duplicate them in our test files.  To keep the test app as close as possible to the production app, we will define `startOptions` in a separate file, and import into `main.ts` and test files.

Create that configuration file (`configuration/configuration.ts`) with this content:

```typescript
import type { StartOptions } from "$fresh/server.ts";

export const startOptions: StartOptions= {
  render: (ctx, render) => {
    ctx.lang = "en-GB";
    render();
  },
}
```

Update the language in line `5` to match your site language.  See the separate <a href="https://rodneylab.com/deno-fresh-html-lang-attribute/">video on the Deno Fresh HTML lang attribute</a> for more on this.

## 🏡 Home Page Tests

Let&rsquo;s add a `tests` folder to the project root directory for our end-to-end tests.  You can add unit tests adjacent to the files they test (`utils/network_test.ts` for example).

We will have a test file in `tests/routes` for each route.  Add a `tests/routes/index_test.ts` file for the index route with this content:

```typescript
import { createHandler } from "$fresh/server.ts";
import type { ServeHandlerInfo } from "$fresh/server.ts";
import { load } from "$std/dotenv/mod.ts";
import { assertEquals, assertStringIncludes } from "$std/testing/asserts.ts";
import { startOptions } from "@/configuration/configuration.ts";
import manifest from "@/fresh.gen.ts";
import { DOMParser } from "deno-dom/deno-dom-wasm.ts";

await load({ envPath: ".env.test", export: true });

const url = "http://127.0.0.1:8001/";

const CONN_INFO: ServeHandlerInfo = {
  remoteAddr: { hostname: "127.0.0.1", port: 53496, transport: "tcp" },
};

Deno.test("Home route", async (t) => {
  const handler = await createHandler(manifest, startOptions);

  await t.step("it returns a 200 status code", async () => {
    const response = await handler(new Request(url), CONN_INFO);
    const { status } = response;
    assertEquals(status, 200);
  });
});
```

We have our first test!  You can see that we mostly rely on JavaScript Web APIs, which will not be a surprise if you&rsquo;ve used Deno before!

A few details are a little different, so let&rsquo;s have a look at them:

- We import **environment variables** in line `9`.  The `envPath` parameter lets you add a separate environment variable file (`.env.test` here) just for testing.  Skip that parameter if you want to use `.env` instead.  Adding the `export: true` option lets you access environment variables using `Deno.env.get('SOME_SECRET')` in your project files.
- We use a Fresh server handler in line `18` to load the page content as the end user sees it.
- Finally, we make an assertion in line `23`, using Deno asserts (imported in line `4`).  See the Deno docs for a <a href="https://deno.land/std@0.196.0/testing/asserts.ts">full list of available Deno assert functions</a>.

Try running your tests from the Terminal with:

```shell
deno task test
```

## 🥱 More Home Page Tests

We mentioned accessing the DOM earlier.  Let&rsquo;s add a couple more tests to see this. We will:

- check the HTML tag includes the `lang` attribute (`<html lang="en-GB">`); and
- that all images have `alt` text, required for accessibility.

Update `tests/routes/index_test.ts`:

```typescript
Deno.test("Home route", async (t) => {
  const handler = await createHandler(manifest, startOptions);

  await t.step("it returns a 200 status code", async () => {
    /* TRUNCATED... */
  });

  await t.step("it sets HTML lang attribute", async () => {
    const response = await handler(new Request(url), CONN_INFO);

    const body = await response.text();
    assertStringIncludes(body, `<html lang="en-GB">`);
  });

  await t.step("images have alt attribute", async () => {
    const response = await handler(new Request(url), CONN_INFO);
    const body = await response.text();
    const doc = new DOMParser().parseFromString(body, "text/html")!;
    const images = doc.getElementsByTagName("img");

    images.forEach((element) => {
      const altText = element.attributes.getNamedItem("alt")?.value;
      assertEquals(altText && typeof altText, "string");
    });
  });
});
```

The way we access the HTML body, using JavaScript Web APIs is probably already familiar to you.  Then, **DOMParser**, again, uses standard APIs.  Hopefully you included alt tags on your own page&rsquo;s images, and these tests pass too!

## 🤖 API Route Tests

For the sake of completeness, let&rsquo;s add a test for an API route.  As you might expect, there is not much difference here.

We will write code to test the Joke API route included in Deno Fresh skeleton projects.  It responds to `GET` requests with a joke in plain text.

Create `tests/routes/api/joke_test.ts` with this content:

```typescript
import { createHandler } from "$fresh/server.ts";
import type { ServeHandlerInfo } from "$fresh/server.ts";
import { load } from "$std/dotenv/mod.ts";
import { assert, assertEquals } from "$std/testing/asserts.ts";
import { startOptions } from "@/configuration/configuration.ts";
import manifest from "@/fresh.gen.ts";

await load({ envPath: ".env.test", export: true });

const url = "http://127.0.0.1:8001/api/joke";

const CONN_INFO: ServeHandlerInfo = {
  remoteAddr: { hostname: "127.0.0.1", port: 53496, transport: "tcp" },
};

Deno.test("Jokes API route", async (t) => {
  const handler = await createHandler(manifest, startOptions);

  await t.step("it returns a joke", async () => {
    const response = await handler(new Request(url, {}), CONN_INFO);
    const { status } = response;
    assertEquals(status, 200);

    const body = await response.text();
    assert(body);
  });

});
```

Hopefully, that was easy to adapt to your own app.

## 🙌🏽 Deno Fresh Testing: Wrapping Up

We saw how you can set up Deno Fresh Testing in your own project.  In particular, we saw:
- how you can **end-to-end test HTML pages** on your Deno Fresh site;
- how you make sure **environment variables** are defined in your tests; and
- a way to **test API routes**.

The <a href="https://github.com/rodneylab/deno/tree/main/demos/deno-fresh-testing">complete code for this post</a>.  I do hope the post has either helped you in testing an existing project or provided some inspiration for getting started with setting up TDD on a new project.

Get in touch if you have some questions about this content or suggestions for improvements.  You can add a comment below if you prefer.  Also, reach out with ideas for fresh content on Deno or other topics!

## 🙏🏽 Deno Fresh Testing: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also, if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on Twitter, <a href="https://toot.community/@rodney">@rodney@toot.community</a> on Mastodon and also the <a aria-label="Join the Rodney Lab Element Matrix room" href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a aria-label="See posts on Astro" href="https://rodneylab.com/tags/astro/">Astro</a> as well as <a aria-label="See posts on deno" href="https://rodneylab.com/tags/deno/">Deno</a>. Also, <a href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.