2

I often hear that a test should contain only one assertion (Disregarding testing attributes of a class) and where the whole pre-conditions for the test should be setup before the assertion.

In our application we have many tests where a user of the application logs in, performs some actions, then the state of the application is asserted, then the user does another thing etc and another assertion is validated. Another test might then only differ in some action taken during the test. For example (Pseudo code)

Test 1:
user = new User()
assertThat(user).is(created)
products = user.buyProducts(2 items)
assertThat(product.length, is(2))


Test 2:
user = new User()
assertThat(user).is(created)
product = user.buyProduct()
assertThat(product, is(notNullValue()))

This leads to tests which are sometimes extremely long (sometimes 50-100 lines, not counting helper methods) and often only differ in one our two statements. Unfortunately, the different statements are often not only variable replacements, so that we cannot use dynamic testing like with Junit5.

Is there another, better way to create the tests? Unfortunately most of our application's tests are setup like this. I also think it might be too much boilerplate to put all these setups into different setup methods and creating test classes which basically stop right in the middle (e.g. after asserting that the user is created), and then creating another test class which only asserts that the product is bought.

1

1 Answer 1

2

The maxim that a test should only have one assertion is intended to make debugging easier. If a test with multiple assertions fails, it is not always immediately apparent which assertion failed. In other words, the rule is intended to save you time spent debugging.

That said, tests take time to execute. And, if you're running them regularly, like you should be, then that time compounds. So, if you have a test suite that requires lots of time consuming setup, it might be worth it to combine multiple assertions into a single test to avoid repeating the setup.

It's a trade off. Either you spend a bit more time debugging, or you spend a bit more time waiting for your tests to run.

In general, I find that unit tests are best kept to a single assertion. These tests are usually small, with minimal/fast setup. Therefore, it makes sense to optimize for faster debugging, since the tests are already fast.

On the other hand, I find that functional tests are less well suited to the single assertion per test rule because these tests typically require more/slower setup. In other words, it's okay to string assertions together so as to optimize for faster test execution.

For the specific example presented above, if creating a User is an inexpensive operation, I would go with one assertion per test:

Test 1:
user = new User()
assertThat(user).is(created)

Test 2:
user = new User()
products = user.buyProducts(2 items)
assertThat(product.length, is(2))

Test 3:
user = new User()
product = user.buyProduct()
assertThat(product, is(notNullValue()))

Otherwise, if User creation is a costly, time consuming operation, I would combine multiple assertions into one test:

Test 1:
user = new User()
assertThat(user).is(created)
product = user.buyProduct()
assertThat(product, is(notNullValue()))
products = user.buyProducts(2 items)
assertThat(product.length, is(2))

The classic example of this is testing an application that requires the user to login before performing any actions. Do you create five different tests, all of which have to login the user first? Or, do you create one test with five assertions?

It's a trade off.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.