I recently built Sazerac to address a problem that I often run into when writing JavaScript tests. I’ve found that in order to create a test suite that is comprehensive, maintainable, and doesn’t have an insane amount of repeated code, I end up using a data-driven approach. Using this approach at Rocket Insights helps us deliver test code that is both useful in catching regressions, and is also easy to read and comprehend.
“Data-driven” tests are boiled down to a set of given inputs and expected outputs.
Encapsulating your application code into pure functions will help facilitate this input/output testing style, and is generally a good practice to follow.
While the latest JS stacks (like React + Redux) encourage a pure functional approach, the most widely used testing frameworks (Jasmine, Mocha, and Jest) aren’t designed with this approach in mind. They often assume that you’ll be testing code with side-effects. Think code “spying”, and (to a certain extent) mocking. These features were created for testing impure functions, but there’s nothing to use specifically for testing pure functions.
I feel strongly about the need for a testing framework with the data-driven approach at its core. Building that framework would be a big undertaking, so I chose a lighter option. I designed Sazerac as a helper library that works with Jasmine, Mocha, and Jest.
I might consider turning Sazerac into a standalone framework, but I’d like to see if people find it useful first.
Let’s look at some code.
This pure function isPrime()
returns true
or false
depending on whether the given parameter num
is a prime number.
Here are some tests for isPrime()
, given the numbers 1
, 2
, 3
, and 4
.
That’s a lot of code to write. If I wanted to add more test cases, I’d have to do some copy and paste. This just doesn’t feel right.
Here are the same tests, using Sazerac.
Sazerac will use these test cases to run **describe**
and **it**
functions that work with Jasmine, Mocha, Jest.
The describe/it messages are set based on given input and expected output, so test reports are super consistent:
isPrime()when given 1✓ should return truewhen given 2✓ should return truewhen given 3✓ should return truewhen given 4✓ should return false
Things are usually more complicated.
Your code isn’t always as simple as the isPrime()
function. Take this React component, for example.
This component expects a product
object with 3 props, title
, price
, and inventory
. If any of those props are missing, it renders an error message and an error
class at the root element. If all the props have values, it renders a success message and no error
class.
I’m going to use Enzyme (awesome for testing React components) to help test this. In my spec file, I’ll create a function to mount the component.
This function’s expected return value is complex, so the .expect()
method that was used for isPrime()
can’t be used here. I’ll use Sazerac’s .assert()
method instead.
In this example, the p
object is the return value of ProductComponent()
given {}
. The test above will pass if the product
element (at the root of the component) has the class error
.
More expectations can be added using additional **.assert()**
calls on the same test case.
Each .assert()
call runs a new test for ProductComponent()
with the given input, {}
. In this example, these 3 tests are run:
ProductComponent()when given {}✓ should render `error` class✓ should display error message✓ should not display success message
We just tested for the “empty” object case, but what about other cases that might result in a similar state? For example, if title
, price
, or inventory
is missing, we would expect the component to render with the same error state.
Sazerac’s **forCases()**
method lets you group test cases that share the same expectations.
Resulting in the following tests:
ProductComponent()when given {}✓ should render `error` class✓ should display error message✓ should not display success messagewhen given {"price":"1.11","inventory":"111"}✓ should render `error` class✓ should display error message✓ should not display success messagewhen given {"title":"p1","inventory":"111"}✓ should render `error` class✓ should display error message✓ should not display success messagewhen given {"title":"p1","price":"1.11"}✓ should render `error` class✓ should display error message✓ should not display success message
This ends up being much more concise than the equivalent tests written with describe
, it
, beforeEach
, etc. It also makes it super easy to add new cases, or to update existing ones as needed.
I’d recommend taking a few of your existing tests and refactoring them to use Sazerac. My hope is that it will reduce the complexity and increase readability of your tests.
If you have feedback, get in touch with me on twitter @MikeCalvanese