JUnit testing conventions and template
Writing unit tests is an essential practice to improve and maintain the quality of any app (or any software project in general). Tests form part of documentation which can help others understand how a piece of code is designed to work (and how it’s designed to fail).
For me, the single greatest benefit that unit tests offer is a safety net when bug fixing or refactoring code to indicate any unintended side effects of changes.
These are a few of the conventions I find useful to adhere as a team when writing JUnit tests to help create consistent and easy-to-read tests. These are specific to JUnit tests in Koltin but many of these conventions would be applicable to other frameworks and languages. Let me know if you find them useful or have your own conventions that you’d like to share and potentially add to the list.
Use MockitoJUnitRunner
Prefer use of @RunWith(MockitoJUnitRunner::class)
and use @Mock
when defining mocks as it will flag when we are unesscessarily mocking in addition to being slightly more concise.
@RunWith(MockitoJUnitRunner::class)
class MyUseCaseTest {
@Mock private lateinit var datastore: LocateDatastore
SUT (System Under Test)
- Use the naming convention sut (System Under Test) for the class being tested.
- Instantiate the
sut
in the setup method marked with @Before annotation.
Naming conventions for test cases
Perfer using the following convention for test case names
@Test
fun `addSnapshot SHOULD insert to DB WHEN DB is empty`() {}
@Test
fun `trackingClick SHOULD start tracking WHEN current state is stopped`() {}
@Test
fun `addSnapshot SHOULD not insert WHEN API throws Error`() {}
Layout of a test case
Follow the Arrange, Act, Assert unit test anatomy
//arrange
whenever(agent.id).thenReturn(null)
//act
sut.updateAssignedAgent(agent)
//assert
verifyZeroInteractions(chatDao)
Named return values “result”
When asserting the return value from a method, I’ve found naming the returned value result
a useful way to increase consistency between test cases.
val result = sut.methodUnderTest()
assertThat(result).isEqualTo(expectedResult)
Prefer assertThat()
to assertEquals
Prefer use of KotlinAssertions.assertThat()
or org.assertj.core.api.Assertions.assertThat()
to assertEquals
as it gives a more useful fail messages. It also allows use of satisfies
to group similar tests together i.e.
assertThat(result).satisfies {
// id is mapped to photoId
assertThat(it.id).isEqualTo(db.photoId)
assertThat(it.imageUrl).isEqualTo(db.imageUrl)
assertThat(it.title).isEqualTo(db.title)
// useful to confirm they haven't been flipped by accident
assertThat(it.location.latitude).isEqualTo(db.latitude)
assertThat(it.location.longitude).isEqualTo(db.longitude)
}
Extension methods for isInstanceOf
These assert isInstanceOf
extensions are useful to improve readablity of JUnit test assertions. I’ve found this most useful when asserting sealed class result objects as you can asset it’s the correct type and then via smart casting continue to assert any specifics of the result, in the sample below I’m asserting the type and then taskId matches expected value.
// use of custom isInstanceOf instead of `isInstanceOf(Success::class.java)`
assertThat(result).isInstanceOf<Success> {
assertThat(it.taskId).isEqualTo(newTaskId)
}
Avoid any()
When using verify(object).method(parameter)
we should use a specific parameter and avoid the any()
as it’s often not specific enough and can potenitally lead to test misses.
JUnit Templete
There doesn’t seem to be a way to modify the File/Code Template when using intention to create a test. I have created this is custom file template which isn’t perfect but it can save time, promote consistency ad includes many of the conventions noted in this article.
I.e we cannot hook into this
Go to Perferences > File and Code Templates and add new template using the below as a guide.
package ${PACKAGE_NAME}
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class ${NAME}Test {
@Mock
private lateinit var dependency: Dependency
private lateinit var sut: ${NAME}
@Before
fun setUp() {
sut = ${NAME}(dependency)
}
@Test
fun `foo SHOULD do something WHEN precondition`(){
whenever(dependency.bar()).thenResult("")
val expectedResult = "a String"
val result = sut.foo()
assertThat(result).isEqualTo(expectedResult)
verify(dependency).bar()
}
}