I really didn’t want to write this, but every good link about structuring unit tests is dead. And I don’t want to hype this too much, but learning to arrange my tests in a certain order was one of the biggest productivity gains in my career.
This is a "Code review" post. This is designed to be copy pasted into code reviews
You should arrange your test into three distinct sections, AAA: Arrange, Act, and Assert. You’ll also see articles touting four sections called SEVT: Setup, Execute, Verify, and Teardown, but in practice, we never put teardown in the actual tests because then they would only run if the test succeeds.
- Arrange – Set up the conditions for the test
- Act – Execute the code
- Assert – Verify your assumptions
If you arrange all your tests like this, then every test will looks familiar even if you’ve never seen before, which mean you’ll be able to understand unfamiliar code much faster.
1. Make the sections obvious
The easiest way to convey the three sections is to group the lines into the three sections separated by a blank line. If your test only has two or three lines, you probably don’t need the blank lines, and if for some reason your test is so long that it can’t fit on one screen, you might need comments to label the sections.
Bad:
expected = {...}
user = {}
stubs...
result = foo(user)
assert result == expected
Better:
user = {}
stubs...
result = foo(user)
expected = {...}
assert result == expected
2. Your first test
I like to make my first test case either a trivial case (for pure functions) or the happy path (for request/response). And I try to get these tests to have zero lines of setup.
Bad:
test("knight is worthy")
user =
id: 1234
email: lancelot@camelot.silly.place
name: Sir Lancelot of Camelot
favorite_color: yellow # This should be blue
quest: to seek the Holy Grail
...a million other things
assert user.is_worthy()
Good:
setUp()
user =
id: 1234
email: lancelot@camelot.silly.place
name: Sir Lancelot of Camelot
favorite_color: yellow # This should be blue
quest: to seek the Holy Grail
...a million other things
test("knight is worthy")
assert user.is_worthy()
2. Copy paste your tests
New test scenarios should be a variation of an existing test; every test starts as a copy paste. This repetition makes your code easy for to read because everything looks familiar! Let’s say we need to check if the user is no longer worthy now.
One consequence is you’ll often end up with extra assertions. I usually keep them, but it’s a balance. If you’re not sure, I ask myself: “is this assertion relevant to the test name?”
3. Make the “arrange” section as small as possible
Minimize the setup for all your tests. The higher up you can get the “act” section of your test, the more readable your tests will be. Remember that the “arrange”/setup section is boilerplate, and we hate boilerplate.
A User
object may require twelve things, but your arrange section should have none of them. Take advantage of fixtures, before
blocks, factories; whatever tools you have at your disposal.
Bad:
user =
id: 1234
email: lancelot@camelot.silly.place
name: Sir Lancelot of Camelot
favorite_color: yellow
quest: to seek the Holy Grail
...a million other things
assert user.is_worthy() == false
Good:
user.favorite_color = blue
assert user.is_worthy() == false
In the “bad” example, it’s not clear why this user fails the worthiness test. The reader is expected to compare it to a good example or to know the data. On top of that, it’s many lines before we actually get past the boilerplate. In the “good” example, it’s clear that the user has the wrong color.
Places this doesn’t work
You’ll be able to split up tests into these sections almost every time, but there are some common exceptions:
- Events, when you need to set up assertions
.on('end', () => { assert ... }
as part of the test setup - Catching exceptions, you’ll be able to arrange in AAA order, but you won’t be able to add line breaks.
Better articles about this subject:
- http://www.agile-code.com/blog/the-anatomy-of-a-unit-test/ (Wayback version)
- https://giang-pham.net/2019/01/07/patterns-in-constructing-unit-tests/ (link is broken)