Using Kotlin for tests

Using Kotlin for tests

Android Testing Boot Camp
TwitterID変えました。@lvla0805 -> @MoyuruAizawa

5f533179da1c82722252cbcb93e7356f?s=128

Moyuru Aizawa

May 10, 2017
Tweet

Transcript

  1. Using Kotlin for tests @lvla0805

  2. lvla lvla0805 Ѫᖒ๖ (Moyuru Aizawa) - Kotlin engineer of FRESH!

    Div. CyberAgent, Inc. - Previously at Pairs Div. Eureka, Inc.
  3. JetBrains/Kotlin v1.1.2 junit-team/junit4 v4.12 mockito/mockito v2.7.22 ntaro/knit v0.1.2 sys1yagi/kmockito v0.1.2

    Note As of April 27
  4. Kotlin?

  5. ‣ JetBrains, OSS ‣ Statically typed programming language ‣ For

    the JVM, Android and the browser ‣ Interoperable with Java Kotlin?
  6. ‣ Type inference ‣ Null safety ‣ Property ‣ Functional

    programming support ‣ Extension functions ‣ Delegation ‣ … Features
  7. Set Up

  8. Preferences -> Plugin -> Install JetBrains plugin… -> Search “Kotlin”

    -> Install Set Up
  9. Tools -> Kotlin -> Configure Kotlin in Project Set Up

  10. dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } Set Up

  11. dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } Set Up

  12. Unit Test

  13. Assertion

  14. public class Person { public final String firstName; public final

    String familyName; public Person(String firstName, String familyName) { this.firstName = firstName; this.familyName = familyName; } public String getFullName() { return firstName + " " + familyName; } } Person
  15. public class Person { public final String firstName; public final

    String familyName; public Person(String firstName, String familyName) { this.firstName = firstName; this.familyName = familyName; } public String getFullName() { return firstName + " " + familyName; } } Person
  16. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", "Aizawa") assertThat(person.fullName, `is`("Moyuru Aizawa")) } } Test Person#getFullName
  17. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", "Aizawa") assertThat(person.fullName, `is`("Moyuru Aizawa")) } } Test Person#getFullName Create a instance of “Person”
  18. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", "Aizawa") assertThat(person.fullName, `is`("Moyuru Aizawa")) } } Test Person#getFullName
  19. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", "Aizawa") assertThat(person.fullName, `is`("Moyuru Aizawa")) } } Test Person#getFullName Property
  20. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", "Aizawa") assertThat(person.fullName, `is`("Moyuru Aizawa")) } } Test Person#getFullName “is” is reserved word of Kotlin
  21. Mock

  22. public class Person { ... @Override public boolean equals(Object o)

    { ... } @Override public int hashCode() { ... } } Person#equals, Person#hashCode
  23. public class PersonDao { public void insert(List<Person> persons) { ...

    } } PersonDao
  24. public class PersonClient { public Single<List<Person>> fetch() { ... }

    } PersonClient
  25. public class PersonRepository { private final PersonDao dao; private final

    PersonClient client; ... public Single<List<Person>> getPersons() { return client.fetch() .doOnSuccess(dao::insert); } } Test PersonRepository#getPersons
  26. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons
  27. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons Mock classes
  28. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons
  29. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons Create expected values [Person(firstName=0, familyName=0), Person(firstName=1, familyName=1), …]
  30. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons Configure return behavior for mock
  31. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons Configure return behavior for mock
  32. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons Configure return behavior for mock
  33. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons “when” is reserved word of Kotlin
  34. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons Invoke PersonRepository#getPersons and assertion
  35. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } Test PersonRepository#getPersons Verify invocation
  36. Write more simple codes

  37. ‣ knit ‣ Junit API set for Kotlin ‣ kmockito

    ‣ Mockito for Kotlin Libraries for Kotlin tests
  38. knit

  39. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", "Aizawa") assertThat(person.fullName, `is`("Moyuru Aizawa")) } } [BEFORE] Test Person#getFullName
  40. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", “Aizawa") person.fullName.should be "Moyuru Aizawa" } } [AFTER] Test Person#getFullName
  41. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", “Aizawa") person.fullName.should be "Moyuru Aizawa" } } How does it work?
  42. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", “Aizawa") person.fullName.should be "Moyuru Aizawa" } } How does it work? Extension property
  43. class PersonTest { @Test fun getFullName() { val person =

    Person("Moyuru", “Aizawa") person.fullName.should be "Moyuru Aizawa" } } How does it work? Infix call
  44. kmockito

  45. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock(PersonDao::class.java) val client = mock(PersonClient::class.java) val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() `when`(client.fetch()).thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) verify(dao).insert(expected) } } [BEFORE] Test PersonRepository#getPersons
  46. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock<PersonDao>() val client = mock<PersonClient>() val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() client.fetch().invoked.thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) dao.verify().insert(expected) } } [AFTER] Test PersonRepository#getPersons
  47. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock<PersonDao>() val client = mock<PersonClient>() val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() client.fetch().invoked.thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) dao.verify().insert(expected) } } How does it work?
  48. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock<PersonDao>() val client = mock<PersonClient>() val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() client.fetch().invoked.thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) dao.verify().insert(expected) } } How does it work? Inline function, reified type parameter
  49. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock<PersonDao>() val client = mock<PersonClient>() val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() client.fetch().invoked.thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) dao.verify().insert(expected) } } How does it work? Extension property
  50. class PersonRepositoryTest { @Test fun getPersons() { val dao =

    mock<PersonDao>() val client = mock<PersonClient>() val repository = PersonRepository(dao, client) val expected = Array(5, Int::toString) .map { Person(it, it) }.toList() client.fetch().invoked.thenReturn(Single.just(expected)) repository.getPersons().test().assertValue(expected) dao.verify().insert(expected) } } How does it work? Extension function
  51. TIPS

  52. Data class

  53. public class Person { public final String firstName; public final

    String familyName; public Person(String firstName, String familyName) { this.firstName = firstName; this.familyName = familyName; } public String getFullName() { return firstName + " " + familyName; } @Override public boolean equals(Object o) { … } @Override public int hashCode() { … } } It is too annoying to override equals/hashCode
  54. data class Person(val firstName: String, val lastName: String) { fun

    getFullName() = firstName + " " + lastName } Use data class The compiler automatically derives equals()/hashCode(), toString(), …
  55. Mock Kotlin classes

  56. ‣ By default, all classes in Kotlin are final ‣

    Mockito cannot mock final classes… Mock Kotlin classes
  57. ‣ Open ‣ Interface ‣ PowerMock ‣ Mockito 2.1.0(or above)

    MockMaker (*experimental) How to mock…?
  58. open class PersonClient { open fun fetch(): Single<List<Person>> { ...

    } } Open
  59. @AllOpen class PersonClient { fun fetch(): Single<List<Person>> { ... }

    } All Open (Kotlin compiler plugin)
  60. interface PersonClient { fun fetch(): Single<List<Person>> } class PersonClientImpl: PersonClient

    { override fun fetch(): Single<List<Person>> { ... } } Interface
  61. val client = PowerMock.mock(PersonClient::class.java) `when`(client.fetch()).thenReturn(Single.just(expected)) PowerMock

  62. Create the file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker containing a single line mock-maker-inline or

    testCompile “org.mockito:mockito-core:2.x.y” testCompile “org.mockito:mockito-inline:2.x.y" Mockito2 MockMaker
  63. ‣ Create the file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker ‣ Configure resolutionStrategy configurations {

    test { resolutionStrategy { force “org.mockito:mockito-core:2.x.y” // 2.1.0 or above } } } Use inline mock maker with kmockito
  64. None
  65. Thank you