1 Test, 1 Assertion?
Jun 30 2016
Should each of our tests have a single assertion, or is it OK for a test to contain multiple assertions?
Ever since I started made a priority of testing, I have flipped back and forth between these two camps. What I have come to realize is that there is a time an place for both!
Here are some guidelines I tend to follow.
Aim for a single assertion per test
The key word here is aim. Sometimes this is easy, sometimes it is more difficult. The single assertion tests are great because the tests can be very clear. When such a test fails, it is usually easier to spot the problem because you don’t need to figure out which assertion failed.
State is difficult to test
In OOP, objects are data + behaviour, and it is common for an object’s behaviour to mutate its data. So how do we test this?
In certain cases you can benefit from multiple assertions here. If you are familiar with “Arrange Act Assert”, then this might be called “Arrange Assert Act Assert”.
Lets look at an example
class IncrementalServer {
private int current = 0;
public void increment()
current++;
}
public int poll() {
return current;
}
}
class IncrementalServerTest {
@Test
public void itIncrements() {
IncrementalServer server = new IncrementalServer();
assertEquals(0, server.poll());
server.increment();
assertEquals(1, server.poll());
}
}
This is a trivial example, and here I would probably add an additional test that server starts at 0, but it demonstrates making an assertion about the initial state. This pattern is useful when the system under test modifies the state of some other object.
The setup is not worth duplicating
Sometimes there is some setup or arranging that needs to take place to test some behaviour, but it wouldn’t be fitting to put the setup in the setUp
method.
You could move such tests into their own test class where you could put all of that setup into the setUp
method, but most of the time that will cost more that it is worth.
One assertion per test can be overkill
Take this equals
method on the fictional Foo class.
class Foo {
Foo(Baz baz, Quux quux) {
this.baz = baz;
this.quux = quux;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o != null && getClass() != o.getClass) return false;
Foo foo = (Foo) o;
if (bar != foo.bar) return false;
return quux == foo.quux;
}
}
Should we use a single test with multiple assertions here? I think so.
@Test
public void equalityIsBasedOnBarAndQuux() {
assertEquals(new Foo("a", "b"), new Foo("a", "b"));
assertNotEquals(new Foo("a", "b"), new Foo("a", "d"));
assertNotEquals(new Foo("a", "b"), new Foo("e", "b"));
assertNotEquals(new Foo("a", "b"), new Foo("c", "d"));
}
You could split these into multiple tests, but you would end up with a test name that like itIsNotEqualWhenBarIsTheSameAndQuuxIsDifferent
and itIsNotEqualWhenBarIsDifferentAndQuuxIsTheSame
. I think equalityIsBasedOnBarAndQuux
is good enough here.
Tips for using multiple assertions in a single test
Be mindful of the test output
Reach for an assertion that gives you a diff
In JUnit, the assertEquals
method will report the expected and actual parameters passed to it.
assertEquals(1, 2);
java.lang.AssertionError:
Expected :1
Actual :2
Add an assertion message
Using an assertion that accepts a message can also make a potential failure clear.
assertTrue("It is greater than 2", 1 > 2);
java.langAssertionError: It is greater than 2
This is will depend on the test framework that you use, but in my experience, most frameworks will offer a way to specify an assertion message.
The moral of the story
Be pragmatic. Sometimes a single assertion per test is the right way to go. Other times multiple assertions can be more effective.
Recent Articles
- Apprenticeship Retro Oct 14 2016
- What the hell is CORS Oct 13 2016
- Cross-site Tracing Oct 11 2016
- Learning Testing Tools First Oct 7 2016