Published in


Writing Test Specs For Humans

Tests aren’t just for catching regression bugs. A good test suite should also serve as documentation for your code.

Any engineer should be able to skim over your tests and know exactly what the code is supposed to do without even looking at the implementation.

There are many different types of testing strategies for different use cases. One technique is called behavior driven development (BDD). The goal of behavior driven development is to define and write your tests in the form of expected business logic. When done properly BDD tests create a beautiful set of documentation for your code that even a non-technical reader can understand.

Let’s look at a couple examples. These examples are in Javascript using Jest but regardless of the language or tools used, the concepts are the same.

Let’s say we are going to test a login page. A spec might look like this:

it('should log the user in', () =>'should show an error when authentication fails', () => ...

At first glance, this spec may look fine. However, as we dive deeper, we notice we are missing some things. What about loading spinners? How about when there is an invalid email? What about logging an event in our analytics service?

BDD helps you better think through these extra user flows that you might otherwise miss. BDD is a type of test driven development, meaning you write your tests before you write your code.

Let’s forget about jest for a second and just talk about what our login page should do:

It should render the login form. When the login button is clicked, it should check for a valid email. If the user entered an invalid email it should show a validation error. If the email is valid, we should show a loading spinner and try to log the user in. If the username and password are valid, it should store an auth token in local storage, add an event to Google Analytics and redirect the user to their NewsFeed. If the login credentials are invalid, it should hide the loading spinner and show an error.

Now let’s break this description down a bit to make it easier to digest:

The login page:
It should render the login form
When the login button is clicked
- It should check for a valid email
When the email is invalid
- It should show a validation error
When the email is valid
- It should show a loading spinner
- It should log the user in
When the login succeeds
- It should store an auth token in localStorage
- It should log an event in Google Analytics
- It should redirect the user to their NewsFeed
When the login fails
- It should hide the loading spinner
- It should show an error message

Now that we’ve broken our spec down into bullets, let’s write some code. I’ve gone ahead and implemented an example based on testing a React component with Enzyme and Jest in this Gist.

If some of the syntax in the Gist is confusing, don’t worry about it. The important part is what is follows each it and describe statement.

With Jest, you use a describe('when ... statement for each condition that could happen. You use an it('should ... statement for each thing that should happen given that condition.

describe('<LoginPage />', () => { 
it('should render the login form', ...
describe('when the login button is clicked', ...
it('should check for a valid email', ...
describe('when the email in invalid', ...
it('should show a validation error', ...

describe('when the email is valid', ...
it('should try to log the user in', ...
it('should show a loading spinner', ...

describe('when login succeeds', ...
it('should store the auth token in local storage', ...
it('should log an event in Google Analytics', ...
it('should redirect the user to their NewsFeed', ...
describe('when login fails', ...
it('should hide the loading spinner', ...
it('should show an error message', ...

You will notice in the Gist, our code follows the exact same format as our bulleted list. Our bulleted list is just our original paragraph with extra spacing. If a reader can read a paragraph in English, they can read our code with over 100 lines of Javascript and make sense of it. No knowledge of software engineering necessary.

BDD is not an end all testing solution. Like anything in software engineering, it has a time and place. In our login page example above, one could argue that a BDD approach is overkill and that it would be more efficient to test that part of our application using Jest snapshots. If we are writing a string parser, it is probably more appropriate to use table driven tests.

However, the idea of thinking through the structure and the process of writing BDD specs before you start your code is a super powerful tool to add to your software engineering toolbelt. BDD will help you better think through the problem at hand and make sure you factor in every different logical path that your user could take.

Next time you write some tests, start with a bulleted list like our example above and see if you catch some logical edge cases you would’ve otherwise missed. At the very least, your co-workers will thank you for the increased readability during code review.

I run a startup called Pesto where we invest in software engineers from underserved communities and then match them with top US tech companies as full-time remote contractors.

Pesto engineers get access to unprecedented educational and mentorship opportunities.

Pesto partners get access to low cost engineering talent to support their core technical team.

If you want to ship faster and cheaper, shoot me a message at



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store