Writing unit tests usually means having to suffer through a lot of boilerplate code. I’m guilty of sometimes writing test cases with an internal understanding of how the code works, resulting in lesser test coverage.
However, ideal test cases are expected to be pure code that express only the test cases and their necessary aspects. To define such a flow in which intention is validated, we can use Specification frameworks like Spek combined with Unit Testing frameworks to test our apps.
What is Spek?
Spek is a Kotlin-based specification framework for JVM. Remember, Spek is not a testing library, which means that we can combine Spek with JUnit, Mockito and even UI tests.
Testing the what instead of the how: By focusing on testing logic without actually describing its flow, we validate intention by differentiating between attributes relevant to the test case and those not.
Writing Spek tests with JUnit
To set up Spek, check out the official documentation here.
Let’s first write a data class in kotlin and call it Person.
To write unit tests with proper coverage for
isPersonRetired(), we would need at least the following tests:
when isActive is true and age is greater than RETIREMENT_AGE
when isActive is true and age is lesser than RETIREMENT_AGE
when isActive is false and age is greater than RETIREMENT_AGE
when isActive is false and age is lesser than RETIREMENT_AGE
In each test, we would need to initialise
Person with all values, regardless of whether or not they’re needed for the particular test.
Let’s write tests in Spek now. With nesting lambdas, we can reduce the duplication in test code and increase readability.
To start, we’ll create the Spek test suite.
We will now initialise
Person in the test suite with the fixture
Spek will execute
beforeEachGroup block immediately before every group, allowing us to modify only the required attributes for each group.
We’ll now use the JUnit Assertions class to write the actual test. Spek allows us to use whatever assertion framework we're most comfortable with.
when isActive is false and age is lesser than RETIREMENT_AGE:
You can look at the entire test suite here.
Patching up tests
One of the pains with unit tests is that if —or when— you change a class, you often spend a good deal of time patching up all your unit tests or refactoring a lot of mock code.
hasPersonWorked to the
Person data class.
With regular unit tests, we would need to modify
Person initialisation in every test. 🙀
With Spek, we can fix initialisation by adding
hasPersonWorked once in the fixture
We’re not done yet because
hasPersonWorked is required by the current test cases and we will need to make changes to the rest of the test suite.
Adding to existing tests
If we were to also check
isPersonRetired() with regular unit tests, it would not only require us to double the number of tests to add
hasPersonWorked, but also make the test names too long to read.
Test names would then be
when isActive is true, age is greater than 60 and hasPersonWorked is true and so on, which would get longer if more conditions are added.
Every additional test layer is another place to make changes in the future. Using Spek significantly reduces risk or iteration time in order to pay for itself.
A very common error with Spek is
java.lang.NoSuchMethodError. Unfortunately, restarting Android Studio will not resolve this issue. 😛
This error happens when the Spek version defined in your dependencies does not match the version used in the Android Studio plugin. It’s good practice to keep a tab on the dependencies that your project is using and check for compatibility when migrating to newer versions of Spek.
I’ve been amazed by how readable (and joyful) unit testing can be by writing declarative test code and I hope you can give Spek a try too!
Tomorrow's blog —the 13th in the Advent Calendar— will be written by @catatsuy. Hope you are looking forward to it! 😸