Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Using Kotlin for tests

Using Kotlin for tests

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

Moyuru Aizawa

May 10, 2017
Tweet

More Decks by Moyuru Aizawa

Other Decks in Programming

Transcript

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

    Div. CyberAgent, Inc. - Previously at Pairs Div. Eureka, Inc.
  2. ‣ JetBrains, OSS ‣ Statically typed programming language ‣ For

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

    programming support ‣ Extension functions ‣ Delegation ‣ … Features
  4. 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
  5. 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
  6. class PersonTest { @Test fun getFullName() { val person =

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

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

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

    Person("Moyuru", "Aizawa") assertThat(person.fullName, `is`("Moyuru Aizawa")) } } Test Person#getFullName Property
  10. 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
  11. public class Person { ... @Override public boolean equals(Object o)

    { ... } @Override public int hashCode() { ... } } Person#equals, Person#hashCode
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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), …]
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. ‣ knit ‣ Junit API set for Kotlin ‣ kmockito

    ‣ Mockito for Kotlin Libraries for Kotlin tests
  24. class PersonTest { @Test fun getFullName() { val person =

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

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

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

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

    Person("Moyuru", “Aizawa") person.fullName.should be "Moyuru Aizawa" } } How does it work? Infix call
  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) } } [BEFORE] Test PersonRepository#getPersons
  30. 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
  31. 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?
  32. 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
  33. 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
  34. 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
  35. 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
  36. data class Person(val firstName: String, val lastName: String) { fun

    getFullName() = firstName + " " + lastName } Use data class The compiler automatically derives equals()/hashCode(), toString(), …
  37. ‣ By default, all classes in Kotlin are final ‣

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

    MockMaker (*experimental) How to mock…?
  39. interface PersonClient { fun fetch(): Single<List<Person>> } class PersonClientImpl: PersonClient

    { override fun fetch(): Single<List<Person>> { ... } } Interface
  40. 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
  41. ‣ 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