Kotlin: Best practice cho Unit test (phần 2)

4. Use Backticks and @Nested Inner Classes

  • Đặt tên của method test trong backticks. Điều này cho phép các "spaces" trong tên phương thức cải thiện khả năng đọc. Bằng cách này, chúng ta không cần thêm annotation @DisplayName.
    
  • @Nested của JUnit5 rất hữu ích để nhóm các method test. Các nhóm có thể là một số type test nhất định (như InputIsXY, ErrorCases) hoặc một nhóm cho từng method được test (GetDesign và UpdateDesign).
    
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class TagClientTest {
    @Test
    fun `basic tag list`() {}

    @Test
    fun `empty tag list`() {}

    @Test
    fun `empty tag translations`() {}

    @Nested
    inner class ErrorCases {
        @Test
        fun `server sends empty body`() {}

        @Test
        fun `server sends invalid json`() {}

        @Test
        fun `server sends 500`() {}

        @Test
        fun `timeout - server response takes too long`() {}

        @Test
        fun `not available at all - wrong url`() {}
    }
}

5. Mockito-Kotlin

Mockito-Kotlin được khuyến khích sử dụng vì nó cung cấp API  thuận tiện và idiomatic. Ví dụ Ví dụ, nó tạo điều kiện cho các type reified của Kotlin. Vì vậy, type có thể được infer và chúng ta không phải chỉ định rõ ràng.
//plain Mockito
val service = mock(TagService::class.java)
setClient(mock(Client::class.java))

//Mockito-Kotlin
val service: TagService = mock()
setClient(mock())

6. Tạo các mock

Việc tạo lại các mock trước mỗi test rất chậm và yêu cầu việc sử dụng lateinit.
/Don't
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DesignControllerTest_RecreatingMocks {

   private lateinit var dao: DesignDAO
   private lateinit var mapper: DesignMapper
   private lateinit var controller: DesignController

   @BeforeEach
   fun init() {
       dao = mock()
       mapper = mock()
       controller = DesignController(dao, mapper)
   }

   // takes 1,5 s!
   @RepeatedTest(300)
   fun foo() {
       controller.doSomething()
   }
}

Thay vào đó, hãy tạo mock instance một lần và đặt lại chúng trước hoặc sau mỗi test. Nó nhanh hơn đáng kể (1,5 giây so với 220 ms trong ví dụ) và cho phép sử dụng các trường không thay đổi với val.

// Do:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DesignControllerTest {

   private val dao: DesignDAO = mock()
   private val mapper: DesignMapper = mock()
   private val controller = DesignController(dao, mapper)

   @BeforeEach
   fun init() {
       reset(dao, mapper)
   }

   // takes 210 ms
   @RepeatedTest(300)
   fun foo() {
       controller.doSomething()
   }
}

** 7. Xử lý các Class với State**

Phương pháp tạo một lần đã được trình bày cho test fixture chỉ hoạt động nếu chúng không có bất kỳ trạng thái nào hoặc có thể được đặt lại dễ dàng (như mocks). Trong các trường hợp khác, việc tạo lại trước mỗi test là không thể tránh khỏi.

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DesignViewTest {

    private val dao: DesignDAO = mock()
    // the class under test has state
    private lateinit var view: DesignView

    @BeforeEach
    fun init() {
        reset(dao)
        view = DesignView(dao)
    }

    @Test
    fun changeButton() {
        assertThat(view.button.caption).isEqualTo("Hi")
        view.changeButton()
        assertThat(view.button.caption).isEqualTo("Guten Tag")
    }
}

Nguồn: https://blog.philipphauer.de/best-practices-unit-testing-kotlin/