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 say we are going to test a login page. A spec might look like this:
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 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 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 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
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.
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.