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

Improve your tests using Kotlin.

Improve your tests using Kotlin.

Fábio Carballo

October 12, 2017
Tweet

More Decks by Fábio Carballo

Other Decks in Technology

Transcript

  1. • Immutability first • Hides Nullability • Allows extensions to

    third-party classes • High-order functions / Lambdas
  2. class Greeter( private val user: User) { fun getEnglishGreeting() =

    "Hello, ${user.fullName()}!" } class User( private val firstName: String, private val lastName: String) { fun fullName(): String = "$firstName $lastName" }
  3. class GreeterTest { @Mock lateinit var user: User lateinit var

    tested: Greeter @Before fun setUp() { MockitoAnnotations.initMocks(this) tested = Greeter(user) } @Test fun englishGreetIsCorrect() { Mockito.`when`(user.fullName()).thenReturn("Fábio Carballo") assertEquals("Hello, Fábio Carballo!", tested.getEnglishGreeting()) } }
  4. class GreeterTest { @Mock lateinit var user: User lateinit var

    tested: Greeter @Before fun setUp() { MockitoAnnotations.initMocks(this) tested = Greeter(user) } @Test fun englishGreetIsCorrect() { Mockito.`when`(user.fullName()).thenReturn("Fábio Carballo") assertEquals("Hello, Fábio Carballo!", tested.getEnglishGreeting()) } } org.mockito.exceptions.base.MockitoException: Cannot mock/spy class fabiocarballo.com.myapplication.User Mockito cannot mock/spy because : - final class
  5. • “Open” Classes open class User( private val firstName: String,

    private val lastName: String) { open fun fullName(): String = "$firstName $lastName" }
  6. Mockito.verify(user).addFriend(Mockito.any()) class User( private val firstName: String, private val lastName:

    String) { fun addFriend(friend: User) { … } } Mockito Matchers rely on null-values.
  7. Mockito.verify(user).addFriend(Mockito.any()) class User( private val firstName: String, private val lastName:

    String) { fun addFriend(friend: User) { … } } Mockito Matchers rely on null-values. Mockito.verify(user).addFriend(null)
  8. mockito-kotlin val user: User = Mockito.mock(User::class) val user: User =

    mock() val user: User = mock { on { fullName() }.thenReturn(“Fábio Carballo”) }
  9. class GreeterTest { @Mock lateinit var user: User lateinit var

    tested: Greeter @Before fun setUp() { MockitoAnnotations.initMocks(this) tested = Greeter(user) } @Test fun englishGreetIsCorrect() { Mockito.`when`(user.fullName()).thenReturn("Fábio Carballo") assertEquals("Hello, Fábio Carballo!", tested.getEnglishGreeting()) } }
  10. class GreeterTest { val user: User = mock() val tested

    = Greeter(user) @Test fun englishGreetIsCorrect() { whenever(user.fullName()).thenReturn("Fábio Carballo") assertEquals("Hello, Fábio Carballo!", tested.getEnglishGreeting()) } }
  11. class GreeterTest { val user: User = mock() val tested

    = Greeter(user) @Test fun englishGreetIsCorrect() { whenever(user.fullName()).thenReturn("Fábio Carballo") assertEquals("Hello, Fábio Carballo!", tested.getEnglishGreeting()) } } class GreeterTest { val user: User = mock { on { fullName() }.thenReturn("Fábio Carballo") } val tested = Greeter(user) @Test fun englishGreetIsCorrect() { assertEquals("Hello, Fábio Carballo!", tested.getEnglishGreeting()) } }
  12. @Test fun test_addFriend_userHasNoFriends_FriendsListShouldNotBeEmpty() { … } @Test fun `when user

    has no friends and a friend is added the friends list should not be empty`() { … }
  13. class ServiceA { fun methodA() { … } } class

    ServiceB { fun methodB() { … } } class Test { fun test() { val serviceA: ServiceA = mock() val serviceB: ServiceB = mock() serviceA.methodA() serviceB.methodB() val inOrder = inOrder(serviceA, serviceB) inOrder.verify(serviceA).methodA() inOrder.verify(serviceB).methodB() } }
  14. class ServiceA { fun methodA() { … } } class

    ServiceB { fun methodB() { … } } class Test { fun test() { val serviceA: ServiceA = mock() val serviceB: ServiceB = mock() serviceA.methodA() serviceB.methodB() val inOrder = inOrder(serviceA, serviceB) inOrder.verify(serviceA).methodA() inOrder.verify(serviceB).methodB() } }
  15. class ServiceA { fun methodA() { … } } class

    ServiceB { fun methodB() { … } } class Test { fun test() { val serviceA: ServiceA = mock() val serviceB: ServiceB = mock() serviceA.methodA() serviceB.methodB() inOrder(serviceA, serviceB).apply { verify(serviceA).methodA() verify(serviceB).methodB() } } }
  16. data class User( private val firstName: String, private val lastName:

    String, private val age: Int) { val fullName = "$firstName $lastName" val canDrink = (age >= 18) val friends: MutableList<User> = mutableListOf() fun addFriend(user: User) { if (user !in friends) { friends.add(user) user.addFriend(this) } } }
  17. @Test fun `the full name is the concatenation of the

    first and last name`() { val user = User(firstName = "Fábio", lastName = "Carballo", age = 26) assertEquals("Fábio Carballo", user.fullName) }
  18. @Test fun `the full name is the concatenation of the

    first and last name`() { val user = User(firstName = "Fábio", lastName = "Carballo", age = 26) assertEquals("Fábio Carballo", user.fullName) user.fullName `should equal` "Fábio Carballo” // Kluent }
  19. @Test fun `the full name is the concatenation of the

    first and last name`() { val user = User(firstName = "Fábio", lastName = "Carballo", age = 26) assertEquals("Fábio Carballo", user.fullName) user.fullName `should equal` "Fábio Carballo” // Kluent } infix fun Any.`should equal`(theOther: Any) = assertEquals(theOther, this)
  20. @Test fun `the full name is the concatenation of the

    first and last name`() { val user = User(firstName = "Fábio", lastName = "Carballo", age = 26) assertEquals("Fábio Carballo", user.fullName) user.fullName `should equal` "Fábio Carballo” // Kluent user.fullName.should.equal(“Fábio Carballo”) // Expect }
  21. @Test fun `friends are correctly added`() { val rui =

    User(firstName = "Rui", lastName = "Gonçalo", age = 28) val fabio = User(firstName = "Fábio", lastName = "Carballo", age = 26) rui.addFriend(fabio) assertEquals(listOf(fabio), rui.friends) // OR assertTrue { fabio in rui.friends } }
  22. @Test fun `friends are correctly added`() { val rui =

    User(firstName = "Rui", lastName = "Gonçalo", age = 28) val fabio = User(firstName = "Fábio", lastName = "Carballo", age = 26) rui.addFriend(fabio) assertEquals(listOf(fabio), rui.friends) // KLUENT rui.friends `should contain` fabio rui.friends `should not contain` rui }
  23. @Test fun `friends are correctly added`() { val rui =

    User(firstName = "Rui", lastName = "Gonçalo", age = 28) val fabio = User(firstName = "Fábio", lastName = "Carballo", age = 26) rui.addFriend(fabio) assertEquals(listOf(fabio), rui.friends) // KLUENT rui.friends `should contain` fabio rui.friends `should not contain` rui // EXPEKT rui.friends.should.contain(fabio) }
  24. @Test fun `friends are correctly added`() { val rui =

    User(firstName = "Rui", lastName = "Gonçalo", age = 28) val fabio = User(firstName = "Fábio", lastName = "Carballo", age = 26) rui.addFriend(fabio) assertEquals(listOf(fabio), rui.friends) // KLUENT rui.friends `should contain` fabio rui.friends `should not contain` rui // EXPEKT rui.friends.should.contain(fabio) fabio.friends.should.have.all.elements(rui).and.have.size(1) }
  25. @Test fun `friends are correctly added`() { val rui =

    User(firstName = "Rui", lastName = "Gonçalo", age = 28) val fabio = User(firstName = "Fábio", lastName = "Carballo", age = 26) rui.addFriend(fabio) assertEquals(listOf(fabio), rui.friends) // KLUENT rui.friends `should contain` fabio rui.friends `should not contain` rui // EXPEKT rui.friends.should.contain(fabio) fabio.friends.should.have.all.elements(rui).and.have.size(1) expect(rui.friends).to.contain(fabio) }
  26. fun drink() { if (canDrink) { System.out.println("Drinking .. ") }

    else { throw UnderageDrinkingException() } } class UnderageDrinkingException() : Exception("You can't drink.")
  27. fun drink() { if (canDrink) { System.out.println("Drinking .. ") }

    else { throw UnderageDrinkingException() } } class UnderageDrinkingException() : Exception("You can't drink.") @Test(expected = UnderageDrinkingException::class) fun `an underage user can't drink`() { val ricardo = User( firstName = "Ricardo", lastName = "Trindade", age = 17) ricardo.drink() }
  28. fun drink() { if (canDrink) { System.out.println("Drinking .. ") }

    else { throw UnderageDrinkingException() } } class UnderageDrinkingException() : Exception("You can't drink.") fun `an underage user can't drink`() { val ricardo = User( firstName = "Ricardo", lastName = "Trindade", age = 17) val ricardoDrinks = { ricardo.drink() }
 
 ricardoDrinks `should throw` UnderageDrinkingException::class }
  29. fun drink() { if (canDrink) { System.out.println("Drinking .. ") }

    else { throw UnderageDrinkingException() } } class UnderageDrinkingException() : Exception("You can't drink.") fun `an underage user can't drink`() { val ricardo = User( firstName = "Ricardo", lastName = "Trindade", age = 17) val ricardoDrinks = { ricardo.drink() }
 
 ricardoDrinks `should throw the Exception` UnderageDrinkingException::class `with message` "You can't drink." }
  30. Hangman •Player 1 thinks about an arbitrary word. •Other players

    try to guess the word by suggesting a letter. •Each wrong guess, a part of the hangman is drawn.
  31. class Hangman(private val word: String) { var wrongAnswerCount = 0

    fun suggestLetter(letter: Char): Boolean { … } }
  32. @RunWith(JUnitPlatform::class) class HangmanFeatureSpec : Spek({ val game = Hangman("apple") describe("letter

    verification") { it("returns true when the suggestion is correct") { game.suggestLetter('a') `should equal` true } it("returns false when the suggestion is incorrect") { game.suggestLetter('f') `should equal` false } } })
  33. @RunWith(JUnitPlatform::class) class HangmanFeatureSpec : Spek({ val game = Hangman("apple") describe("letter

    verification") { it("returns true when the suggestion is correct") { game.suggestLetter('a') `should equal` true } it("returns false when the suggestion is incorrect") { game.suggestLetter('f') `should equal` false } } })
  34. @RunWith(JUnitPlatform::class) class HangmanBehaviorSpec : Spek({ val hiddenWord = "apple" given("a

    new game with the hidden word being $hiddenWord:") { val game = Hangman(hiddenWord) on(“correct suggestion") { game.suggestLetter(‘a’) it("has no wrong answers") { game.wrongAnswerCount `should equal` 0 } it("shows the guessed letters") { game.dashedWord `should equal` "a----" } } } })
  35. @RunWith(JUnitPlatform::class) class HangmanBehaviorSpec : Spek({ val hiddenWord = "apple" given("a

    new game with the hidden word being $hiddenWord:") { val game = Hangman(hiddenWord) on(“correct suggestion") { game.suggestLetter(‘a’) it("has no wrong answers") { game.wrongAnswerCount `should equal` 0 } it("shows the guessed letters") { game.dashedWord `should equal` "a----" } } } })
  36. fun SpecBody.describe(description: String, body: SpecBody.() -> Unit) { group("describe $description",

    body = body) } fun SpecBody.context(description: String, body: SpecBody.() -> Unit) { group("context $description", body = body) } fun SpecBody.given(description: String, body: SpecBody.() -> Unit) { group(“given $description", body = body) }
  37. Ignoring tests • Just add an X as a prefix

    to the scope xit("returns true when the suggestion is correct") { game.suggestLetter('a') `should equal` true }
  38. Ignoring tests • Just add an X as a prefix

    to the scope xit("returns true when the suggestion is correct") { game.suggestLetter('a') `should equal` true }