Four rules to ensure your unit tests are following the Single Responsibility Principle — the “S” in S.O.L.I.D.
1. Each Test Has a Single Call to The Production Code
The first mistake we can make is to have a single test to check different things. This often happens when the use case grows in scope and we don’t take time to refactor the test or break it into multiple tests.
👎test with multiple calls to the production code👎
As a rule of thumb, the “given” and “then” parts of our test can have multiple lines, but the “when” part should ideally be a one-liner.
In our case, we can achieve this by breaking the test into two smaller tests:
👍tests with a single call to the production code👍
2. Each Test Has a Single Logical Assertion
A single logical assertion means a single reason for failing.
For instance, if we check that the promotion status has been updated and that the user has been added to the promotion, these are two different logical assertions because they can fail for different reasons:
👎test with two (different) logical assertions👎
On the other hand, we are allowed to check multiple things about the same object.
For example, we can check if the user has access to the promotion and if the user’s status was updated. These two physical assert statements are part of the same logical assertion which verifies that the user was correctly updated.
👍test with a single logical assertion 👍
3. There Is No State Persisted Between Tests
Having the state persist between tests it is a big code smell. The tests should be completely isolated and the order of running them shouldn’t matter.
If we are using a database, all data should be cleared after each test. An alternative solution to this would be to make the tests transactional and roll back after each test.
Ideally, tests should be able to run in parallel. This can greatly speed up the testing job from the pipeline.
4. Avoid Test Duplication
How often did it happen to introduce a small mistake in the code and break many tests? Most of the time this can be a sign of test duplication.
As a rule, any small mistake in the core domain logic should break the least number of tests — ideally only one.
5. Conclusion
In this article, we discovered that we need to test a single scenario at a time by having a single call to production and a single logical assertion per test.
Additionally, we need to avoid test duplication and tests that are not completely isolated from each other.
This being said, we should be aware of what we are testing, and not only how to test it. It is important to test the behavior of our application, and not only the implementation itself.
If you want to dive deeper into this subject, I wrote a dedicated article about how to write beautiful and reliable unit tests.
If you respect these rules and somebody comes to you asking “how SOLID are your tests?” — you can surely answer “SOLID as a rock!”.
Photo by Karmishth Tandel on Unsplash