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

Advocating against the use of mocks in tests

Vlad
June 11, 2019

Advocating against the use of mocks in tests

Why mocks should not be used in tests

Vlad

June 11, 2019
Tweet

More Decks by Vlad

Other Decks in Programming

Transcript

  1. Example interface CreditCardTransactor {c fun checkCreditCardState(creditCard: String): CreditCardState fun pay(

    transaction: Transaction, creditCard: String, dollars: BigDecimal ): Payment fun beginTransaction(): Transaction fun rollBack(transaction: Transaction) fun competeTransaction(transaction: Transaction) }cc
  2. Example class CardPaymentsProcessor( private val creditCardTransactor: CreditCardTransactor ) : PaymentProcessor

    {c override fun processPayment(creditCard: String, amount: BigDecimal) { when (creditCardTransactor.checkCreditCardState(creditCard)) { CreditCardState.Gucci -> transact(creditCard, amount) CreditCardState.Stolen -> callPolice() }c }c }c
  3. Example class CardPaymentsProcessor( private val creditCardTransactor: CreditCardTransactor ) : PaymentProcessor

    {c override fun processPayment(creditCard: String, amount: BigDecimal) { when (creditCardTransactor.checkCreditCardState(creditCard)) { CreditCardState.Gucci -> transact(creditCard, amount) CreditCardState.Stolen -> callPolice() }c }c }c
  4. Example private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }cc creditCardTransactor.competeTransaction(transaction) }cc
  5. Test @Test fun spendingMoneyLikeNoTomorrow() {c }c c override fun processPayment(creditCard:

    String, amount: BigDecimal) {c when (creditCardTransactor.checkCreditCardState(creditCard)) {c CreditCardState.Gucci -> transact(creditCard, amount) CreditCardState.Stolen -> callPolice() }b }b
  6. Test override fun processPayment(creditCard: String, amount: BigDecimal) {c when (creditCardTransactor.checkCreditCardState(creditCard))

    {c CreditCardState.Gucci -> transact(creditCard, amount) CreditCardState.Stolen -> callPolice() }b }b @Test fun spendingMoneyLikeNoTomorrow() {c }c
  7. Test override fun processPayment(creditCard: String, amount: BigDecimal) {c when (creditCardTransactor.checkCreditCardState(creditCard))

    {c CreditCardState.Gucci -> transact(creditCard, amount) CreditCardState.Stolen -> callPolice() }b }b @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) }c
  8. Test override fun processPayment(creditCard: String, amount: BigDecimal) {c when (creditCardTransactor.checkCreditCardState(creditCard))

    {c CreditCardState.Gucci -> transact(creditCard, amount) CreditCardState.Stolen -> callPolice() }b }b @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) }c
  9. Test override fun processPayment(creditCard: String, amount: BigDecimal) {c when (creditCardTransactor.checkCreditCardState(creditCard))

    {c CreditCardState.Gucci -> transact(creditCard, amount) CreditCardState.Stolen -> callPolice() }b }b @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) }c
  10. Test private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) }c
  11. Test private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) }c
  12. Test private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(transactor.pay(any(), any(), any())).thenReturn(payment) }c
  13. Test private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(transactor.pay(any(), any(), any())).thenReturn(payment) }c
  14. Test private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(transactor.pay(any(), any(), any())).thenReturn(payment) }c
  15. Test private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(payment.isOvercharge()).thenReturn(false) whenever(transactor.pay(any(), any(), any())).thenReturn(payment) }c
  16. Test private fun transact(creditCard: String, amount: BigDecimal) {c …c creditCardTransactor.competeTransaction(transaction)

    }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(payment.isOvercharge()).thenReturn(false) whenever(transactor.pay(any(), any(), any())).thenReturn(payment) subject.processPayment("not-even-real", BigDecimal.TEN) }c
  17. Test private fun transact(creditCard: String, amount: BigDecimal) {c …c creditCardTransactor.competeTransaction(transaction)

    }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(payment.isOvercharge()).thenReturn(false) whenever(transactor.pay(any(), any(), any())).thenReturn(payment) subject.processPayment("not-even-real", BigDecimal.TEN) verify(transactor).competeTransaction(transaction) }c
  18. Test private fun transact(creditCard: String, amount: BigDecimal) {c … creditCardTransactor.competeTransaction(transaction)

    }c @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(payment.isOvercharge()).thenReturn(false) whenever(transactor.pay(any(), any(), any())).thenReturn(payment) subject.processPayment("not-even-real", BigDecimal.TEN) verify(transactor).competeTransaction(transaction) }c
  19. private fun transact(creditCard: String, amount: BigDecimal) {c val transaction =

    creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c 1 2 3
  20. private fun transact(creditCard: String, amount: BigDecimal) {c val transaction =

    creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }c creditCardTransactor.competeTransaction(transaction) }c 1 2 3
  21. @Test fun spendingMoneyLikeNoTomorrow() {c val subject = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci)

    val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(payment.isOvercharge()).thenReturn(false) whenever(transactor.pay(any(), any(), any())).thenReturn(payment) val transactorInOrder = inOrder(transactor) subject.processPayment("not-even-real", BigDecimal.TEN) transactorInOrder.verify(transactor).beginTransaction() transactorInOrder.verify(transactor).pay( transaction, "not-even-real", BigDecimal.TEN ) transactorInOrder.verify(transactor).competeTransaction(transaction) transactorInOrder.verifyNoMoreInteractions() }cc
  22. @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject

    = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(payment.isOvercharge()).thenReturn(false) whenever(transactor.pay(any(), any(), any())).thenReturn(payment) val transactorInOrder = inOrder(transactor) subject.processPayment("not-even-real", BigDecimal.TEN) transactorInOrder.verify(transactor).beginTransaction() transactorInOrder.verify(transactor).pay( transaction, "not-even-real", BigDecimal.TEN ) transactorInOrder.verify(transactor).competeTransaction(transaction) transactorInOrder.verifyNoMoreInteractions() }cc
  23. A few problems • Implementation details are leaked into the

    test • The test is unreadable • Refactoring is still very dangerous
  24. Example interface CreditCardTransactor {c fun checkCreditCardState(creditCard: String): CreditCardState fun pay(

    transaction: Transaction, creditCard: String, dollars: BigDecimal ): Payment fun beginTransaction(): Transaction fun rollBack(transaction: Transaction) fun competeTransaction(transaction: Transaction) }cc
  25. Example interface CreditCardTransactor {c fun checkCreditCardState(creditCard: String): CreditCardState fun inTransaction(doInTransaction:

    Transaction.() -> Unit) }c interface Transaction {c fun rollback() fun pay( creditCard: String, dollars: BigDecimal ): Payment }c
  26. Example private fun transact(creditCard: String, amount: BigDecimal) {c val transaction

    = creditCardTransactor.beginTransaction() val payment = creditCardTransactor.pay(transaction, creditCard, amount) if (payment.isOvercharge()) {c creditCardTransactor.rollBack(transaction) }cc creditCardTransactor.competeTransaction(transaction) }cc
  27. Example private fun transact(creditCard: String, amount: BigDecimal) {c creditCardTransactor.inTransaction {c

    val payment = pay(creditCard, amount) if (payment.isOverCharge()) {c rollback() }cc }z }cc
  28. @Test fun spendingMoneyLikeNoTomorrow() {c val transactor = mock<CreditCardTransactor>() val subject

    = CardPaymentProcessor(transactor) whenever(transactor.checkCreditCardState("not-even-real")) .thenReturn(CreditCardState.Gucci) val transaction = mock<Transaction>() whenever(transactor.beginTransaction()).thenReturn(transaction) val payment = mock<Payment>() whenever(payment.isOvercharge()).thenReturn(false) whenever(transactor.pay(any(), any(), any())).thenReturn(payment) val transactorInOrder = inOrder(transactor) subject.processPayment("not-even-real", BigDecimal.TEN) transactorInOrder.verify(transactor).beginTransaction() transactorInOrder.verify(transactor).pay( transaction, "not-even-real", BigDecimal.TEN ) transactorInOrder.verify(transactor).competeTransaction(transaction) transactorInOrder.verifyNoMoreInteractions() }cc
  29. • Implementation details are leaked into the test • The

    test is unreadable • Refactoring is still very dangerous A few problems
  30. • Implementation details are leaked into the test • The

    test is unreadable • Refactoring is still very dangerous • Has this test provided value? A few problems
  31. Loaded gun • Keep the testing compact and readable •

    Avoid coding a tautology • Don’t mock everything! it’s an anti pattern • Don’t mock value objects! • Don’t mock a type you don’t own! • …and a few more. https://github.com/mockito/mockito/wiki/How-to-write-good-tests
  32. @Test fun buyingThingsWithoutRegrets() {c val transactor = FakeCreditCardTransactor() val subject

    = CardPaymentProcessor(transactor) val creditCardNumber = "very-real-much-money" subject.processPayment(creditCardNumber, BigDecimal.TEN) }c Life without mocks
  33. @Test fun buyingThingsWithoutRegrets() {c val transactor = FakeCreditCardTransactor() val subject

    = CardPaymentProcessor(transactor) val creditCardNumber = "very-real-much-money" subject.processPayment(creditCardNumber, BigDecimal.TEN) assertEquals( actual = transactor.getLastPaymentAmount(creditCardNumber), expected = BigDecimal.TEN ) }c Life without mocks
  34. @Test fun buyingThingsWithRegrets() {c val transactor = FakeCreditCardTransactor() val subject

    = CardPaymentProcessor(transactor) val creditCardNumber = "very-real-no-money" subject.processPayment(creditCardNumber, BigDecimal.TEN) assertEquals( actual = transactor.getLastPaymentAmount(creditCardNumber), expected = null ) }c Life without mocks
  35. • Functionality focused test • Invocation and assertions, nothing else

    • Refactoring is safe • Not only tests Life with fakes
  36. What is the catch? • Way more thought goes into

    the fake implementation • Complex fakes might have complex implementations
  37. What is the catch? • Way more thought goes into

    the fake implementation • Complex fakes might have complex implementations • What else?
  38. Literature used • Marcel Weiher “Why I don’t mock” https://news.ycombinator.com/item?id=7809402

    • Martin Fowler “Mocks aren’t stubs” https://martinfowler.com/articles/ mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs • Google ToT “Don’t overuse mocks” https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html • Google ToT “Testing state vs testing interaction” https://testing.googleblog.com/2013/03/testing-on-toilet-testing-state-vs.html • Google ToT “Fake your way to better tests” https://testing.googleblog.com/2013/06/testing-on-toilet-fake-your-way- to.htmlhttps://testing.googleblog.com/2013/06/testing-on-toilet-fake-your-way-to.html • r/Androiddev “Do you unit test? If you do, do you use mocking libraries?”
 https://www.reddit.com/r/androiddev/comments/7l3wrd/android_devs_do_you_unit_test_if_you_do_do_you/ • Jake Wharton “Mockito is a loaded gun” https://twitter.com/jakewharton/status/1103836521327706113 • Eric Smith “Thats not yours” https://8thlight.com/blog/eric-smith/2011/10/27/thats-not-yours.html • Mockito “How to write good tests” https://github.com/mockito/mockito/wiki/How-to-write-good-tests • Uncle Bob “When to mock” https://blog.cleancoder.com/uncle-bob/2014/05/10/WhenToMock.html • Uncle Bob “The Little Mocker” https://blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html • Jesse Wilson “Value objects, Service Objects and Glue“ https://publicobject.com/2019/06/10/value-objects-service-objects-and- glue/
  39. Fin