Testing with Dojo

dojo testing

When you use the Dojo cli to create an application, it comes with a full test framework for you to use. If like me, you aren’t as diligent with your tests as you maybe you could be, that’s ok, there’s no testing police that will give you a citation, only your own shame when you try to track down how you broke some feature after some updates you made. I kid, I kid. But, it is a good idea to have some tests written to make sure core features of your application don’t break as you write new code.

When you first create a Dojo application, it will already provide you with some unit tests that you can use as a guide.

Default Dojo Installation

You can find this sample repo on github.

Let’s see what a unit test looks like.

Running Tests

You can run these tests using npm test and you should get results like below.

Dojo testing results

So what does one of these tests look like?

// tests/unit/widgets/Profile.ts
const { describe, it } = intern.getInterface("bdd");
import harness from "@dojo/framework/testing/harness";
import { w, v } from "@dojo/framework/widget-core/d";

import Profile from "../../../src/widgets/Profile";
import * as css from "../../../src/widgets/styles/Profile.m.css";

describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, { username: "Dojo User" }));
    h.expect(() => v("h1", { classes: [css.root] }, ["Welcome Dojo User!"]));
  });
});

When testing widgets, you are basically testing that the output of the widget is what you expect, especially when given a set of properties. Ideally, rendering a widget is going to be pure function, meaning they should be pretty easy to test when given the same input.

The Dojo framework comes with a harness helper, which can be used to test your widgets. You can give it a widget, and check that the virtual DOM output is what you expect.

  • Does it render as expected?
  • Does a child widget or element render as expected?
  • Do event handlers work as expected?

Assertion Templates

The Profile widget we tested above has a property username that we can test against in the output. We could rewrite the entirety of the expected virtual DOM output for each test (that’s a lot of typing) or we could create an assertion to test against that would allow us to change the expected properties on each run.

Let’s see how that would work.

First, I need to update the Profile widget slightly since the username property is required. We can make it optional and provide a default value in the widget.

// src/widgets/Profile.ts
export interface ProfileProperties {
  username?: string;
}

export default class Profile extends WidgetBase<ProfileProperties> {
  protected render() {
    const { username } = this.properties;
    return v("h1", { classes: [css.root] }, [
      `Welcome ${username || "Stranger"}!`
    ]);
  }
}

This is a little safer anyway. Now in my test, I can create my assertion template.

// tests/unit/widgets/Profile.ts
// Add the assertionTemplate module
import assertionTemplate from "@dojo/framework/testing/assertionTemplate";
...

// Create my assertion
const profileAssertion = assertionTemplate(() =>
  v("h1", { classes: [css.root], "~key": "welcome" }, ["Welcome Stranger!"])
);

describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, {}));
    // Test against my base assertion
    h.expect(profileAssertion);
  });
});

We can test against our base assertion like we did before. In our assertion template, we add a ~key property to the node so that we can update its expected output. In a tsx file, this is called assertion-key.

We can now test the output if we provide a given property to the widget.

// src/tests/unit/widgets/Profile.ts
describe("Profile", () => {
  it("default renders correctly", () => {
    const h = harness(() => w(Profile, {}));
    h.expect(profileAssertion);
  });

  it("renders given username correctly", () => {
    // update the expected result with a given username
    const namedAssertion = profileAssertion.setChildren("~welcome", [
      "Welcome Kel Varnsen!"
    ]);
    const h = harness(() => w(Profile, { username: "Kel Varnsen" }));
    h.expect(namedAssertion);
  });
});

What the ~key allows is for us to update that expected portion of our assertion template. So if I provide a username, I should expect a different welcome message. The assertionTemplate.setChildren() returns a new assertion template you can reference so that you don’t need to reset it after each unit test, which is incredibly useful and I think is a nice touch to the library.

You can read more about assertion templates and some of its other useful methods in the documentation.

Summary

This was just a quick look at testing with Dojo, but I think it highlights how useful the provided tools are for you test your widgets! Dojo tests use intern by default, so you can look at the docs on how to test the business logic of your applications as well. An added benefit here is that intern provides functional tests, so you can test the behavior of your application as a user would interact with it. This would require a blog post of its own, but you can look at the Dojo todo-mvc example to see how it uses functional tests.

Now I know everyone is going to go out and write unit tests for all their code!

Dojo from the Blocks

Dojo blocks

One of the low-key features that was released in Dojo 5 was the introduction of Blocks. Blocks go hand-in-hand with Dojo build time rendering.

What Blocks allow you to do is run some arbitrary code in a node environment during the build process.

Build time rendering is a great tool you can use to generate static content without having to worry about any server side component to generate pages as requested.

For example, you could use Blocks to preprocess images that you might want loaded into your page, or maybe a more common use case of converting markdown to use for you blog or site. Blocks give you the flexibility to run code you might normally run in the server environment during your build process.

Building a block

Maybe I want to build my blog on top of Dojo, and I want to just write my articles in markdown. I can use a library like showdown to parse my markdown files to HTML. Here is a very basic module that can do this.

// src/blocks/markdown.block.ts
import * as fs from 'fs';
import { resolve } from 'path';

import { Converter } from 'showdown';

const mdConverter = new Converter();

export default function (path: string) {
  path = resolve(__dirname, path);
  const file = fs.readFileSync(path, 'utf8');
  // convert Markdown to HTML
  const html = mdConverter.makeHtml(file);
  return html
};

Blocks are types of metas you can use in your widgets. I can use my block by calling the meta, and running it with with the needed arguments, like the path to the markdown file I want to parse.

import WidgetBase from "@dojo/framework/widget-core/WidgetBase";
import { dom } from "@dojo/framework/widget-core/d";
import Block from "@dojo/framework/widget-core/meta/Block";
import { tsx } from "@dojo/framework/widget-core/tsx";

import fromMarkdown from "../blocks/markdown.block";

import * as css from "./styles/About.m.css";

export default class About extends WidgetBase {
  protected render() {
    const node = document.createElement("div");
    // Use my block
    const message = this.meta(Block).run(fromMarkdown)(
      "../../markdown/post2.md"
    );
    node.innerHTML = message;
    // Create a vnode to inject my HTML
    const vnode = dom({ node });
    return (
      <div>
        <h1 classes={css.root}>About Page</h1>
        {vnode}
      </div>
    );
  }
}

I can now naively inject my parsed markdown as HTML into my page. Ideally, I would like to convert that HTML into real virtual dom nodes, but I haven’t gotten that far yet.

You can quickly see how useful this would be during build time to process files, maybe pull in some external files and use them in an app.

Image processing

In my app, I might have some images that I want to convert to base64 strings so I can embed them. I can use a tool like sharp to resize my images. When I do, I can go ahead create the virtual dom nodes and return them in my block.

// src/blocks/imagebase64.block.ts
import { resolve } from 'path';
import { v } from '@dojo/framework/widget-core/d';
import * as sharp from 'sharp';

export default async function (path: string) {
  path = resolve(__dirname, path);
  // resize my images
  const images = [
    await sharp(path).resize(200).toBuffer(),
    await sharp(path).resize(300).toBuffer(),
    await sharp(path).resize(400).toBuffer(),
    await sharp(path).resize(500).toBuffer()
  ];

  return images.map((a) =>
    v('img', { src: `data:image/jpeg;base64, ${a.toString('base64')}`, alt: 'sally' })
  );
};

You might notice, that I’m able to run asynchronous tasks inside my block. This allows me to do some more interesting things like image processing, fetching data, or maybe run some sort of analysis on a dataset to create formatted json that can be used by a charting library! I’m just throwing out some ideas here!

Summary

You can view the source code for this sample here, and you can view a live demo here.

Dojo Blocks are really interesting, and I think they provide a whole new level of functionality for developers taking advantage of build time rendering with Dojo. I don’t see a reason not to use build time rendering, and Blocks offer you a whole new opportunity to get crazy about it. I’m currently looking at a rewrite of my blog with Dojo using them!