Rethinking Unit Test Assertions

Eric Elliott
Oct 8, 2018 · 5 min read
  1. What should it do? (Prose description)
  2. What was the actual output?
  3. What was the expected output?
  4. How do you reproduce the failure?
describe('addEntity()', async ({ pass, fail }) => {
const myEntity = { id: 'baz', foo: 'bar' };
try {
const response = await addEntity(myEntity);
const storedEntity = await getEntity(response.id);
pass('should add the new entity');
} catch(err) {
fail('failed to add and read entity', { myEntity, error });
}
});
  1. What should it do? 'should add the new entity'
  2. What was the actual output? Oops. We don’t know. We didn’t supply this data to the testing framework.
  3. What was the expected output? Again, we don’t know. We’re not testing a return value here. Instead, we’re assuming that if it doesn’t throw, everything worked as expected — but what if it didn’t? We should be testing the resulting value if the function returns a value or resolving promise.
  4. How do you reproduce the failure? We can see this a little bit in the test setup, but we could be more explicit about this. For example, it would be nice to have a prose description of the input that you’re feeding in to give us a better understanding of the intent of the test case.

equal() is my favorite assertion. If the only available assertion in every test suite was equal(), almost every test suite in the world would be better for it.”

In the years since I wrote that, I doubled down on that belief. While testing frameworks got busy adding even more “convenient” assertions, I wrote a thin wrapper around Tape that only exposed a deep equality assertion. In other words, I took the already minimal Tape library, and removed features to make the testing experience better.

  • Isolated (for unit tests) or Integrated (for functional and integration tests, test should be isolated and components/modules should be integrated)
  • Thorough, and
  • Explicit
assert({
given: Any,
should: String,
actual: Any,
expected: Any
}) => Void
describe('sum()', async assert => {
assert({
given: 'no arguments',
should: 'return 0',
actual: sum(),
expected: 0
});
});
TAP version 13
# sum()
ok 1 Given no arguments: should return 0
describe('addEntity()', async assert => {
const myEntity = { id: 'baz', foo: 'bar' };
const given = 'an entity';
const should = 'read the same entity from the api';
try {
const response = await addEntity(myEntity);
const storedEntity = await getEntity(response.id);
assert({
given,
should,
actual: storedEntity,
expected: myEntity
});
} catch(error) {
assert({
given,
should,
actual: error,
expected: myEntity
});
}
});
  1. What should it do? 'given an entity: should read the same entity from the api'
  2. What was the actual output? { id: 'baz', foo: 'bar' }
  3. What was the expected output? { id: 'baz', foo: 'bar' }
  4. How do you reproduce the failure? Now the instructions to reproduce the test are more explicitly spelled out in the message: The given and should descriptions are supplied.

Is a Deep Equality Assertion Really Enough?

I have been using RITEway on an almost-daily basis across several large production projects for almost a year and a half. It has evolved a little. We’ve made the interface even simpler than it originally was, but I’ve never wanted another assertion in all that time, and our test suites are the simplest, most readable test suites I have ever seen in my entire career.

npm install --save-dev riteway

Simple tests are better tests.

P.S. I’ve been using the term “unit tests” throughout this article, but that’s just because it’s easier to type than “automated software tests” or “unit tests and functional tests and integration tests”, but everything I’ve said about unit tests in this article applies to every automated software test I can think of. I like these tests much better than Cucumber/Gherkin for functional tests, too.

Next Steps

TDD Day is an online recorded webinar deep dive on test driven development, different kinds of tests and the roles they play, how to write more testable software, and how TDD made me a better developer, and how it can do the same for you. It’s a great master class to help you or your team reach the next level of TDD practice, featuring 5 hours of video content and interactive quizzes to test your memory.


JavaScript Scene

JavaScript, software leadership, software development, and related technologies.

Eric Elliott

Written by

Make some magic. #JavaScript

JavaScript Scene

JavaScript, software leadership, software development, and related technologies.