Why we created Rhum for testing Deno projects
Eric Crooks
Posted on June 24, 2020
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)
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)
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:
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", () => { ... });
});
});
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
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)
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)
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.
Posted on June 24, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.