Annotation-based javascript unit tests with saul
From the saul demo.
There’s too much test boilerplate running around in Javascript projects IMHO. There are genuine cases in which some use-cases require well-thought out non-trivial test cases. But most test cases I see in the wild follow some variation of:
import Utils from 'utils';
import { expect } from 'chai';
import { myFunc, myOtherFunctionWhichCallsUtl } from 'myModule';
describe('My awesome suite', () => {
it('should produce proper output', () => {
const actual = myFunc(arg1, arg2, arg3);
const expected = { foo: 'bar' }
expect(actual).to.eql(expected);
});
it('should call the utilFunction', () => {
Utils.utilFunction = sinon.spy(); // mocks the utilFunction
myOtherFunctionWhichCallsUtl();
expect(Utils.utilFunction.calledOnce).to.equal(true)
});
});
What if we can reduce this effort with a custom DSL? Or better yet, with a DSL, that allows us to co-locate our tests with functions using annotations? Something like this:
// @t "produces output" myFunc('foo', 'bar', 'baz') ~equals {'foo': 'bar'}
export function myFunc(arg1, arg2, arg3) {
/* ... function content ... */
}
// @t "calls util" myOtherFunctionWhichCallsUtil(spy('util')) ~expect spy('util').calledOnce
export function myOtherFunctionWhichCallsUtil(utilFn = Utils.utilFunction) {
/* ... function content ... */
utilFn();
}
This is what I tried to do with . It lets you write tests alongside your code for most of the cases, and it generates framework agnostic tests on the fly and feed it to your test runner — whether it’s mocha, jest or jasmine.
A few benefits I’ve noted in my own projects include:
-
Self-documentation: Annotated tests provide some documentation as to what the tested artifact accomplishes — and it runs a valid test as well.
-
Forces me to write more testable code: When I feel-like I can’t write a meaningful test for a function with saul, it indicates to me that maybe the function is doing “too much” or “too little to have a meaningful test”.
-
Allows me to test domain-specific use-cases: If I have a bunch of tests that I need to write something very specific to my domain, I can easily use a custom-engine and assert that logic. Like:
// @t "foo" foo(1) ~does-my-domain-specific-stuff
export function foo(arg) {
// does something domain specific
}
// @t "foo" foo(1) ~does-my-domain-specific-stuff
export function bar(arg) {
// does something domain specific, similar to foo()
}
// @t "foo" foo(1) ~does-my-domain-specific-stuff
export function baz(arg) {
// does something domain specific, similar to foo() and bar()
}
That’s the spiel! Check out . Happy testing!