DEV Community

Eric Crooks
Eric Crooks

Posted on • Edited on

Why we created Rhum for testing Deno projects

Rhum is a lightweight testing framework for Deno. It uses zero dependencies outside of Deno's Standard Modules and works with deno test under the hood.

This article dives deeper into the "why" we created Rhum and for what purpose.

An article written by @craigmorten dives deeper into using Rhum. Link is here https://dev.to/craigmorten/how-to-write-spec-tests-in-deno-55e8.

The Problem

The Drash Land team and I noticed that testing Drash was starting to become unwieldy. Drash has many unit tests and the output was something like:

test parseBodyAsJson: can parse JSON bodies... ok (2ms)
test setHeaders(): attaches headers the request ... ok (2ms)
test getMimeType(): file is not a URL ... ok (3ms)
test getMimeType(): file is a URL ... ok (4ms)
Enter fullscreen mode Exit fullscreen mode

While testing, we noticed that the output was not as descriptive as we wanted/needed it to be to help us debug failing tests. Failing tests were not easily noticeable through the output. So next we tried to be more descriptive with the tests to get the following output:

test http_service_test.ts | parseBodyAsJson: can parse JSON bodies... ok (2ms)
test http_service_test.ts | setHeaders(): attaches headers the request ... ok (2ms)
test http_service_test.ts | getMimeType(): file is not a URL ... ok (3ms)
test http_service_test.ts | getMimeType(): file is a URL ... ok (4ms)
Enter fullscreen mode Exit fullscreen mode

With this setup, a failing test would show us what test file to look in and what test to look for. This worked for a while, but then the output grew and it became a bit hard to read. See the screenshot below:

Drash - Rhum

Not only that, our tests files became hard to read because we did not have a syntax like Mocha's nested describe and it syntax.

The Solution

To make things easier for us, we decided to work on a module (which later became Rhum) to help us write tests like Mocha and Baretest. We also wanted the output to be nice. Our thoughts on the syntax were something like the following:

testPlan("the_test_file.ts", () => {
  testSuite("methodOne()", () => {
    testCase("returns true when it does the thing", () => { ... });
    testCase("returns false if it can't do the thing", () => { ... });
  });
  testSuite("methodTwo()", () => {
    testCase("returns true when it does the thing", () => { ... });
    testCase("returns false if it can't do the thing", () => { ... });
  });
});
Enter fullscreen mode Exit fullscreen mode

We used testPlan, testSuite, and testCase to follow QA practices. This is testing after all.

Our thoughts on the resulting output were something like the following:

the_test_file.ts
    methodOne()
        returns true when it does the thing
        returns false if it can't do the thing
    methodTwo()
        returns true when it does the thing
        returns false if it can't do the thing
Enter fullscreen mode Exit fullscreen mode

With some very helpful guidance from @bartlomieju from the Deno team, we were able to build Rhum and have it work for us in a way that makes writing tests easier and reading output easier.

Before Rhum, we had the following output in Drash:

test services/http_request_service_test.ts | accepts() | Asserting: accepts the single type if it is present in the header ... ok (1ms)
test services/http_request_service_test.ts | accepts() | Asserting: rejects the single type if it is not present in the header ... ok (1ms)
test services/http_request_service_test.ts | accepts() | Asserting: accepts the first of multiple types if it is present in the header ... ok (1ms)
test services/http_request_service_test.ts | accepts() | Asserting: accepts the second of multiple types if it is present in the header ... ok (1ms)
test services/http_request_service_test.ts | accepts() | Asserting: rejects the multiple types if none are present in the header ... ok (0ms)
test services/http_request_service_test.ts | getCookie() | Asserting: Returns the cookie value if it exists ... ok (1ms)
test services/http_request_service_test.ts | getCookie() | Asserting: Returns undefined if the cookie does not exist ... ok (0ms)
test services/http_service_test.ts | Asserting: getMimeType(): file is not a URL ... ok (3ms)
test services/http_service_test.ts | Asserting: getMimeType(): file is a URL ... ok (4ms)

test result: ok. 116 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (839ms)

Enter fullscreen mode Exit fullscreen mode

Now we have the following:

services/http_request_service_test.ts
    accepts()
        accepts the single type if it is present in the header ... ok (1ms)
        rejects the single type if it is not present in the header ... ok (1ms)
        accepts the first of multiple types if it is present in the header ... ok (1ms)
        accepts the second of multiple types if it is present in the header ... ok (1ms)
        rejects the multiple types if none are present in the header ... ok (1ms)
    getCookie()
        Returns the cookie value if it exists ... ok (1ms)
        Returns undefined if the cookie does not exist ... ok (1ms)

services/http_service_test.ts
    getMimeType()
        file is not a URL ... ok (3ms)
        file is a URL ... ok (3ms)

test result: ok. 116 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (770ms)
Enter fullscreen mode Exit fullscreen mode

Not only that, the test files in Drash are much cleaner with nested test suites and test cases.

If you are testing your Deno project and running into the issues we came across, I recommend you give Rhum a shot. It may solve your problems as it did for us.

Thanks for reading!

Eric

Special Thanks

@bartlomieju for showing us the following issue to base our development on: https://github.com/denoland/deno/issues/4092

All who participated in the poll for the name. It was tied between Rhum and Bourbon. We decided to close the poll and have Alexa flip a coin for us. Rhum won.

@melhirech for suggesting Rhum in the polls.

Top comments (0)