best practices for unit testing in kotlin
play

Best Practices for Unit Testing in Kotlin @philipp_hauer - PowerPoint PPT Presentation

Best Practices for Unit Testing in Kotlin @philipp_hauer Spreadshirt KotlinConf, Amsterdam Oct 05, 2018 Question My First Test in Kotlin... open class UserRepository Bolla! class UserControllerTest { op


  1. Best Practices for Unit Testing in Kotlin @philipp_hauer Spreadshirt KotlinConf, Amsterdam Oct 05, 2018

  2. Question

  3. My First Test in Kotlin...

  4. open class UserRepository Bo�l���la��! class UserControllerTest { op�� ��qu���� companion object { mu���l�! re����g�a�l�! @JvmStatic lateinit var controller: UserController bu�, @JvmStatic lateinit var repo: UserRepository s�a��c! @BeforeClass @JvmStatic initialize() { repo = mock() controller = UserController(repo) } } @Test Har� �� R�ad! fun findUser_UserFoundAndHasCorrectValues() { Bet��� `when`(repo.findUser(1)).thenReturn(User(1, "Peter" )) Moc� val user = controller.getUser(1) assertEquals(user?.name, "Peter" ) AP�? Po�r E���r Me���g� } 4 }

  5. We can do better! Re�d���e Idi����ic Cle�� Con���� Re�s��a�l� F�i���� Mes����s 5

  6. How? Tes� ����c��le Nam���, Gro����g Tes� ���r��i�� Moc� ���d���g The ����� of Sp�i�g I��eg����on Dat� ����se� 6

  7. Recap: Idiomatic Kotlin Code

  8. Idiomatic Kotlin Code Immutability Non-Nullability val String var String? No Static Access No direct language feature 8

  9. Test Class Lifecycle

  10. JUnit4: Always New Test Class Instances class RepositoryTest { Exe����d �o� val mongo = startMongoContainer() e�c� te�� @Test instance1: RepositoryTest fun test1() { ... } @Test instance2: RepositoryTest fun test2() { ... } } Where to put the initial setup code? 10

  11. JUnit4: Static for the Initial Setup Code nu�� Bo�l���la��! class RepositoryTest { wo���r�u�� companion object { mu���l� @JvmStatic private lateinit var mongo: GenericContainer s�a��c @JvmStatic private lateinit var repo: Repository @BeforeClass @JvmStatic fun initialize() { mongo = startMongoContainer() repo = Repository(mongo.host, mongo.port) } } } 11

  12. JUnit5 to the Rescue! 12

  13. JUnit5: Reuse the Test Class Instance @TestInstance(TestInstance.Lifecycle.PER_CLASS) class RepositoryTest { private val mongo = startMongoContainer().apply { configure() } private val repo = Repository(mongo.host, mongo.port) @Test Con���� fun test1() { } Idi����ic } 13

  14. JUnit5: Reuse the Test Class Instance @TestInstance(TestInstance.Lifecycle.PER_CLASS) class RepositoryTest { private val mongo: GenericContainer private val repo: Repository init { mongo = startMongoContainer().apply { configure() } repo = Repository(mongo.host, mongo.port) } } 14

  15. JUnit5: Change the Lifecycle Default src/test/resources/junit-platform.properties: junit.jupiter.testinstance.lifecycle.default = per_class @TestInstance(TestInstance.Lifecycle.PER_CLASS) 15

  16. Test Names and Grouping

  17. Backticks class TagClientTest { @Test fun `basic tag list`() {} @Test fun `empty tag list`() {} } 17

  18. Whi�� t��� b��o�g� to ���c� ��t�o�? 18

  19. @Nested Inner Classes class DesignControllerTest { @Nested inner class GetDesigns { @Test fun `all fields are included`() {} getDesign() @Test fun `limit parameter`() {} } @Nested inner class DeleteDesign { @Test deleteDesign() fun `design is removed in db`() {} } } 19

  20. 20

  21. Kotlin Test Libraries

  22. Being Spoilt for Choice Mocking Test Frameworks Assertions Kotlin Spek Strikt Atrium Mockito-Kotlin HamKrest Expekt KotlinTest MockK Kluent AssertK Java JUnit5 AssertJ My �e�s���� c�o��� (fo� ��w) Incomplete list. Some libraries fit into multiple categories. 22

  23. Test-Specific Extension Functions assertThat(taxRate1).isCloseTo(0.3f, Offset.offset(0.001f)) assertThat(taxRate2).isCloseTo(0.2f, Offset.offset(0.001f)) assertThat(taxRate3).isCloseTo(0.5f, Offset.offset(0.001f)) Dup����ti�� fun AbstractFloatAssert<*>.isCloseTo(expected: Float) = this.isCloseTo(expected, Offset.offset(0.001f)) // Usage: Cle�� assertThat(taxRate1). isCloseTo (0.3f) assertThat(taxRate2). isCloseTo (0.2f) Idi����ic assertThat(taxRate3). isCloseTo (0.5f) 23

  24. Mock Handling

  25. Classes Are Final by Default Solutions ● Interfaces ● open explicitly ● Mockito: Enable incubating feature to mock final classes ● MockK 25

  26. MockK mockk(relaxed= true ) val clientMock: UserClient = mockk() every { clientMock.getUser(any()) } returns User(id = 1, name = "Ben" ) val updater = UserUpdater(clientMock) updater.updateUser(1) verify { clientMock.getUser(1) } 26

  27. MockK verifySequence { clientMock.getUser (2) repoMock.saveUser(user) } java.lang.AssertionError: Verification failed: calls are not exactly matching verification sequence Matchers: UserClient(#5).getUser( eq(2) )) UserRepo(#4).saveUser(eq(User(id=1, name=Ben, age=29)))) Calls: 1) UserClient(#5).getUser( 1 ) 2) UserRepo(#4).saveUser(User(id=1, name=Ben, age=29)) 27

  28. Does Test Speed Matter? 2 s �o� 31 Uni� T��t�? 28

  29. Don't Recreate Mocks class DesignControllerTest { private lateinit var repo: DesignRepository private lateinit var client: DesignClient private lateinit var controller: DesignController @BeforeEach fun init() { Ex�e�s���! repo = mockk() client = mockk() controller = DesignController(repo, client) } } 29

  30. Create Mocks Once, Reset Them class DesignControllerTest { private val repo: DesignRepository = mockk() private val client: DesignClient = mockk() private val controller = DesignController(repo, client) @BeforeEach fun init() { Fas� clearMocks(repo, client) } } 30

  31. Create Mocks Once, Reset Them 2.1 s 0.4 s 31

  32. Handle Classes with State class DesignViewTest { private val repo: DesignRepository = mockk() s�a��f�� private lateinit var view: DesignView @BeforeEach fun init() { clearMocks(repo) re-c�e���on ����ir�� view = DesignView(repo) } @Test fun changeButton() { assertThat(view.button.caption).isEqualTo( "Hi" ) view.changeButton() assertThat(view.button.caption).isEqualTo( "Guten Tag" ) } 32 }

  33. Spring Integration

  34. All-Open Compiler Plugin @Configuration class SpringConfiguration{ @Bean fun objectMapper() = ObjectMapper().registerKotlinModule() } BeanDefinitionParsingException: Configuration problem: @Configuration class 'SpringConfiguration' may not be final . < dependency > < compilerPlugins > < groupId >org.jetbrains.kotlin</ groupId > < plugin >spring</ plugin > < artifactId >kotlin-maven-allopen</ artifactId > </ compilerPlugins > < version >${kotlin.version}</ version > </ dependency > 34

  35. Constructor Injection for Spring-free Testing @Component class DesignController( private val designRepo: DesignRepository, private val designClient: DesignClient, ) {} Eas� �� t��� Log�� ��t�o�� Sp���g: val repo: DesignRepository = mockk() val client: DesignClient = mockk() val controller = DesignController(repo, client) 35

  36. Utilize Data Classes

  37. Data Classes for Assertions org.junit.ComparisonFailure: expected:<[2]> but was:<[1]> Expected :2 ??? Actual :1 assertThat(actualDesign.id).isEqualTo(2) assertThat(actualDesign.userId).isEqualTo(9) assertThat(actualDesign.name).isEqualTo( "Cat" ) 37

  38. Data Classes for Assertions val expectedDesign = Design(id = 2, userId = 9, name = "Cat" ) assertThat(actualDesign).isEqualTo(expectedDesign) org.junit.ComparisonFailure: expected:<Design(id=[2], userId=9, name=Cat...> but was:<Design(id=[1], userId=9, name=Cat...> Expected :Design(id=2, userId=9, name=Cat) Actual :Design(id=1, userId=9, name=Cat) se��-ex���n��o�y 38

  39. Data Classes for Assertions assertThat(actualDesigns).containsExactly( Design(id = 1, userId = 9, name = "Cat" ), Design(id = 2, userId = 4, name = "Dog" ) ) Expecting: <[Design(id=1, userId=9, name=Cat), Gre��! Design(id=2, userId=4, name= Dogggg )]> to contain exactly (and in same order): <[Design(id=1, userId=9, name=Cat), Design(id=2, userId=4, name= Dog )]> but some elements were not found: <[Design(id=2, userId=4, name= Dog )]> and others were not expected: <[Design(id=2, userId=4, name= Dogggg )]> 39

  40. Data Classes for Assertions Single Element assertThat(actualDesign) . isEqualToIgnoringGivenFields (expectedDesign,"id") assertThat(actualDesign) . isEqualToComparingOnlyGivenFields (expectedDesign,"name") Lists assertThat(actualDesigns) . usingElementComparatorIgnoringFields ("id") .containsExactly(expectedDesign1, expectedDesign2) assertThat(actualDesigns) . usingElementComparatorOnFields ("name") .containsExactly(expectedDesign1, expectedDesign2) 40

  41. Helper Function for Object Creation val testDesign = Design( id = 1, userId = 9 name = "Fox" , dateCreated = Instant.now(), - Blo��� c��e tags = mapOf() ) - Are ��� p���s �e��v��� val testDesign2 = Design( fo� �h� ���t? id = 2, userId = 9 name = "Cat" , dateCreated = Instant.now(), tags = mapOf() ) 41

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend