JUnit testing conventions and template

JUnit testing conventions and template

2023, Mar 21    

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 SHOULD WHEN

@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 Create Test

Go to Perferences > File and Code Templates and add new template using the below as a guide.

Create new code template

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()
    }
}