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

Fakes and Mocks and Spies - Oh My! -- Chicago

Rick Busarow
December 10, 2019

Fakes and Mocks and Spies - Oh My! -- Chicago

Let’s talk about testing support. There’s no shortage of opinions on how to handle subject dependencies, but no option fits all scenarios. Do we use a mocking framework, or write out test-specific implementations? I’ll share tips on writing maintainable, useful tests quickly.

Rick Busarow

December 10, 2019
Tweet

More Decks by Rick Busarow

Other Decks in Programming

Transcript

  1. Tests should… break when the app breaks pass when the

    app is working be easy to read be fast to write @rbusarow
  2. @rbusarow import org.junit.Test class ExampleUnitTest { @Test fun addition_isCorrect() {

    val result = 2 + 2 assertEquals(4, result) } } JUnit assertEquals
  3. @rbusarow import org.junit.Test class ExampleUnitTest { @Test fun addition_isCorrect() {

    val result = 2 + 3 assertEquals(4, result) } } java.lang.AssertionError: Expected :4 Actual :5
  4. @rbusarow import org.junit.Test class ExampleUnitTest { @Test fun addition_isCorrect() {

    val result = 2 + 3 assertEquals(result, 4) } } wrong order!
  5. @rbusarow import org.junit.Test class ExampleUnitTest { @Test fun addition_isCorrect() {

    val result = 2 + 3 assertEquals(result, 4) } } java.lang.AssertionError: Expected :5 Actual :4 wrong order!
  6. @rbusarow infix assertions import org.junit.Test class ExampleUnitTest { @Test fun

    addition_isCorrect() { val result = 2 + 3 result shouldBe 4 } }
  7. @rbusarow infix assertions import io.kotlintest.shouldBe import org.junit.Test class ExampleUnitTest {

    @Test fun addition_isCorrect() { val result = 2 + 3 result shouldBe 4 } }
  8. @rbusarow infix assertions import io.kotlintest.shouldBe import org.junit.Test class ExampleUnitTest {

    @Test fun addition_isCorrect() { val result = 2 + 3 result shouldBe 4 } } org.opentest4j.AssertionFailedError: expected: 4 but was: 5 Expected :5 Actual :4
  9. @rbusarow infix assertions import io.kotlintest.shouldBe import org.junit.Test @Test fun more()

    { someBoolean shouldBe true someNullable shouldBe null someList shouldBe listOf(1, 2, 3) }
  10. @rbusarow infix assertions import io.kotlintest.shouldBe import org.junit.Test @Test fun more()

    { someBoolean shouldBe true someNullable shouldBe null someList shouldBe listOf(1, 2, 3) someArray shouldBe arrayOf(1, 2, 3) }
  11. @rbusarow Mocking class AnalyticsSolution private constructor() { fun report(message: String):

    Unit = ... companion object { fun initialize(context: Context): Unit = ... fun instance(): AnalyticsSolution = ... } }
  12. @rbusarow Mocking class AnalyticsSolution private constructor() { fun report(message: String):

    Unit = ... companion object { fun initialize(context: Context): Unit = ... fun instance(): AnalyticsSolution = ... } } Final by default
  13. @rbusarow Mocking class AnalyticsSolution private constructor() { fun report(message: String):

    Unit = ... companion object { fun initialize(context: Context): Unit = ... fun instance(): AnalyticsSolution = ... } } Private constructor Final by default
  14. @rbusarow Mocking class AnalyticsSolution private constructor() { fun report(message: String):

    Unit = ... companion object { fun initialize(context: Context): Unit = ... fun instance(): AnalyticsSolution = ... } } Private constructor Final by default Not JVM
  15. @rbusarow Mocking class AnalyticsSolution private constructor() { fun report(message: String):

    Unit = ... companion object { fun initialize(context: Context): Unit = ... fun instance(): AnalyticsSolution = ... } } interface AnalyticsWrapper { fun report(event: String) } class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution ) : AnalyticsWrapper { override fun report(event: String) { analytics.report(event)
  16. @rbusarow Mocking } } interface AnalyticsWrapper { fun report(event: String)

    } class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution ) : AnalyticsWrapper { override fun report(event: String) { analytics.report(event) } }
  17. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `wrapper report

    should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) } }
  18. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `wrapper report

    should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val subject = AnalyticsWrapperImpl(solution) } }
  19. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `wrapper report

    should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val subject = AnalyticsWrapperImpl(solution) subject.report("test event") } }
  20. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `wrapper report

    should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val subject = AnalyticsWrapperImpl(solution) subject.report("test event") verify { solution.report("test event") } } }
  21. @rbusarow Mocking class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution ) :

    AnalyticsWrapper { override fun report(event: String) { analytics.report(event) } }
  22. @rbusarow Mocking class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution, private val

    appVersion: AppVersion ) : AnalyticsWrapper { override fun report(event: String) { if (appVersion.debug) { Log.v("AnalyticsWrapper", event) } else { analytics.report(event) } } }
  23. @rbusarow Mocking class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution, private val

    appVersion: AppVersion ) : AnalyticsWrapper { override fun report(event: String) { if (appVersion.debug) { Log.v("AnalyticsWrapper", event) } else { analytics.report(event) } } } ????
  24. @rbusarow given: a = b b = c then: a

    = c transitive relation transitive property
  25. @rbusarow given: if a then b if b then c

    then: if a then c chain rule transitive implication hypothetical syllogism double modus ponens
  26. @rbusarow Mocking class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution, private val

    appVersion: AppVersion ) : AnalyticsWrapper { override fun report(event: String) { if (!appVersion.debug) { reportToSolution(event) } else { log(event) } } internal fun reportToSolution(event: String) { performComplexTask() analytics.report(event) } internal fun log(event: String) { Log.v(“AnalyticsWrapper”, event) } } A B B C
  27. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `report in

    release build should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() every { appVersion.debug } returns false val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) subject.report("test event") verify { subject.reportToSolution("test event") } } }
  28. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `report in

    release build should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() every { appVersion.debug } returns false val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) subject.report("test event") verify { subject.reportToSolution("test event") } } }
  29. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `report in

    release build should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() every { appVersion.debug } returns false val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) subject.report("test event") verify { subject.reportToSolution("test event") } } }
  30. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `report in

    release build should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() every { appVersion.debug } returns false val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) subject.report("test event") verify { subject.reportToSolution("test event") } } }
  31. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `reportToSolution should

    delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) every { subject.performComplexTask() } just runs subject.report("test event") verify { subject.performComplexTask() } verify { solution.report("test event") } } }
  32. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `reportToSolution should

    delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) every { subject.performComplexTask() } just runs subject.report("test event") verify { subject.performComplexTask() } verify { solution.report("test event") } } }
  33. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `reportToSolution should

    delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) every { subject.performComplexTask() } just runs subject.report("test event") verify { subject.performComplexTask() } verify { solution.report("test event") } } }
  34. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `reportToSolution should

    delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) every { subject.performComplexTask() } just runs subject.report("test event") verify { subject.performComplexTask() } verify { solution.report("test event") } } }
  35. @rbusarow Mocking class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution, private val

    appVersion: AppVersion ) : AnalyticsWrapper { override fun report(event: String) { if (!appVersion.debug) { reportToSolution(event) } else { log(event) } } internal fun reportToSolution(event: String) { performComplexTask() analytics.report(event) } internal fun log(event: String) { Log.v(“AnalyticsWrapper”, event) } } A B B C
  36. @rbusarow Mocking class AnalyticsWrapperImpl( private val analytics: AnalyticsSolution, private val

    appVersion: AppVersion ) : AnalyticsWrapper { override fun report(event: String) { if (!appVersion.debug) { reportToSolution(event) } else { log(event) } } internal fun reportToSolution(event: String) { performComplexTask() } internal fun log(event: String) { Log.v(“AnalyticsWrapper”, event) } } A B B ???
  37. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `reportToSolution should

    delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) every { subject.performComplexTask() } just runs subject.report("test event") verify { subject.performComplexTask() } verify { solution.report("test event") } } } Fails
  38. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `reportToSolution should

    delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) every { subject.performComplexTask() } just runs subject.report("test event") verify { subject.performComplexTask() } } }
  39. @rbusarow Mocking internal class AnalyticsWrapperImplTest { @Test fun `report in

    release build should delegate to AnalyticsSolution`() { val solution: AnalyticsSolution = mockk(relaxed = true) val appVersion: AppVersion = mockk() every { appVersion.debug } returns false val subject = spyk(AnalyticsWrapperImpl(solution, appVersion)) subject.report("test event") verify { subject.reportToSolution("test event") } } }
  40. @rbusarow class SomePresenterTest { @Test fun `onImportantButtonClick should report to

    AnalyticsWrapper`() { val analyticsWrapper: AnalyticsWrapper = mockk(relaxed = true) val subject = SomePresenter(analyticsWrapper) subject.onImportantButtonClick() verify { analyticsWrapper.report("important click") } } }
  41. @rbusarow class SomePresenterTest { @Test fun `onImportantButtonClick should report to

    AnalyticsWrapper`() { val analyticsWrapper: AnalyticsWrapper = mockk(relaxed = true) val subject = SomePresenter(analyticsWrapper) subject.onImportantButtonClick() verify { analyticsWrapper.report("important click") } } }
  42. @rbusarow class SomePresenterTest { @Test fun `onImportantButtonClick should report to

    AnalyticsWrapper`() { val analyticsWrapper: AnalyticsWrapper = mockk(relaxed = true) val subject = SomePresenter(analyticsWrapper) subject.onImportantButtonClick() verify { analyticsWrapper.report("important click") } } }
  43. @rbusarow interface AnalyticsWrapper { fun report(event: String) } class FakeAnalyticsWrapper

    : AnalyticsWrapper { override fun report(event: String) { history.add(event) } }
  44. @rbusarow interface AnalyticsWrapper { fun report(event: String) } class FakeAnalyticsWrapper

    : AnalyticsWrapper { val history = mutableListOf<String>() override fun report(event: String) { history.add(event) } fun reset() = history.clear() }
  45. @rbusarow class SomePresenterTest { @Test fun `onImportantButtonClick should report to

    AnalyticsWrapper`() { val analyticsWrapper: AnalyticsWrapper = mockk(relaxed = true) val subject = SomePresenter(analyticsWrapper) subject.onImportantButtonClick() verify { analyticsWrapper.report("important click") } } }
  46. @rbusarow class SomePresenterTest { @Test fun `onImportantButtonClick should report to

    AnalyticsWrapper`() { val analyticsWrapper = FakeAnalyticsWrapper() val subject = SomePresenter(analyticsWrapper) subject.onImportantButtonClick() analyticsWrapper.history shouldBe listOf("important click") } }
  47. @rbusarow class FakeEventDao : EventDao { val store = mutableMapOf<String,

    Event>() override fun getAll(): List<Event> = store.values.toList() override fun getById(id: String): Event? = store[id] override fun insert(event: Event) { store[event.id] = event } }
  48. @rbusarow data class Event( val id: String, val message: String,

    val syncTimestamp: Long?, val source: String )
  49. @rbusarow object FakeEvent { fun create( id: String = ID,

    message: String = MESSAGE, syncTimestamp: Long? = null, source: String = SOURCE ) = Event( id = id, message = message, syncTimestamp = syncTimestamp, source = source ) const val ID = "EVENT_ID" const val MESSAGE = "EVENT_MESSAGE" const val SOURCE = "EVENT_SOURCE" }