Slide 1

Slide 1 text

This is a Mock Slide Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 2

Slide 2 text

Hi! Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 3

Slide 3 text

Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 4

Slide 4 text

This is the talk about test doubles Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 5

Slide 5 text

Not the talk by the head of Test Double Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 6

Slide 6 text

Justin isn't here, so I can mock him... Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 7

Slide 7 text

expect(:justin).to recieve(:this_talk) .and_return(:polite_dissent) Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 8

Slide 8 text

So, I wrote a book Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 9

Slide 9 text

And I wrote some tests Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 10

Slide 10 text

it "calculates price where tickets have discounts" do ticket_one = spy(price_check: 1000) ticket_two = spy(price_check: 1000) discount = spy() calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(2000)) expect(ticket_one).to have_received(:price_check).with(discount) expect(ticket_two).to have_received(:price_check).with(discount) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 11

Slide 11 text

Then I un-wrote some tests Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 12

Slide 12 text

it "calculates price where tickets have discounts" do ticket_one = Ticket.create(price_cents: 2000) ticket_two = Ticket.create(price_cents: 2000) discount = DiscountCode.create(discount_percentage: 25) calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(3000)) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 13

Slide 13 text

Why did I do that? Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 14

Slide 14 text

I've been ambivalent about test doubles for years Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 15

Slide 15 text

"I have to say, as much as I love using mocks and stubs to cover hard-to-reach objects and states, my own history with very strict behavior-based mock test structures hasn’t been great. My experience was that writing all the mocks around a given object tended to be a drag on the test process. But I’m wide open to the possibility that this method works better for others or that I’m not doing it right." Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 16

Slide 16 text

Three years later Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 17

Slide 17 text

"My opinion about the best way to use mock objects changes every few months. I’ll try some mocks, they’ll work well, I’ll start using more mocks, they’ll start getting in the way, I’ll back off, and then I’ll think, “Let’s try some mocks.” This cycle has been going for years and I have no reason to think it’s going to change anytime soon." Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 18

Slide 18 text

Why does this keep happening to me? Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 19

Slide 19 text

What can I do about it? Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 20

Slide 20 text

Test Doubles are not to be Mocked Noel Rappin, Table XI (@noelrap) Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 21

Slide 21 text

I'm not talking about this: Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 22

Slide 22 text

it "handles a stripe failure" do token = instance_double(StripeCharge, success: false) allow(StripeCharge).to receive(:create).and return(token) workflow.run expect(workflow.status).to eq(:failure) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 23

Slide 23 text

Non controversial usage replacing heavyweight objects specifying failure states Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 24

Slide 24 text

What makes a test "good"? Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 25

Slide 25 text

SWIFT Straightforward, Well-Defined, Independent, Fast, Truthful Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 26

Slide 26 text

Long term priorities might be different Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 27

Slide 27 text

A good test Leads to well-designed code Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 28

Slide 28 text

What is "well-designed code"? Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 29

Slide 29 text

Clear Easy to change Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 30

Slide 30 text

How? Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 31

Slide 31 text

Domain discovery Behavior validation Safety net Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 32

Slide 32 text

A Tale of Two Tests Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 33

Slide 33 text

Calculate the total price of multiple tickets, given a discount Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 34

Slide 34 text

State test it "calculates price where tickets have discounts" do ticket_one = Ticket.create(price_cents: 2000) ticket_two = Ticket.create(price_cents: 2000) discount = DiscountCode.create(discount_percentage: 25) calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(3000)) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 35

Slide 35 text

Spy Test (!) it "calculates price where tickets have discounts" do ticket_one = spy(price_check: 1000) ticket_two = spy(price_check: 1000) discount = spy() calculator = PriceCalculator.new([ticket_one, ticket_two], [discount]) calculator.calculate! expect(calculator.price_cents.to eq(2000)) expect(ticket_one).to have_received(:price_check).with(discount) expect(ticket_two).to have_received(:price_check).with(discount) end Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 36

Slide 36 text

State: uses real objects, expectation on state Spy !: uses test doubles, expectations on behavior Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 37

Slide 37 text

Straightforward: State Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 38

Slide 38 text

Well-defined Tie Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 39

Slide 39 text

Independent Spy Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 40

Slide 40 text

Fast Spy Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 41

Slide 41 text

Truthful Let's examine... Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 42

Slide 42 text

If the Calculator has a bug: State fails Spy fails Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 43

Slide 43 text

If the Ticket has a bug State fails Spy passes Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 44

Slide 44 text

If the Ticket doesn't exist State fails Spy passes Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 45

Slide 45 text

So far, this seems bad for the Spy test Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 46

Slide 46 text

The code can be wrong and the double test will pass Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 47

Slide 47 text

This is a matter of perspective Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 48

Slide 48 text

And this is not the only test in the suite Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 49

Slide 49 text

Let's say the discount logic gets more complex Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 50

Slide 50 text

If the ticket API changes: Team State fails Team Double passes Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 51

Slide 51 text

The state test fails even though neither the code or the test changed Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 52

Slide 52 text

The test can fail even though the test is right Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 53

Slide 53 text

Design goal: A failure state causes exactly one test to fail Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 54

Slide 54 text

Doubles test the design and the behavior Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 55

Slide 55 text

The double encourages the creation of additional tests Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 56

Slide 56 text

This is good because Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 57

Slide 57 text

It encourages good design practices Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 58

Slide 58 text

This is bad because Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 59

Slide 59 text

The design might change Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 60

Slide 60 text

It is hard to drive the next failing test Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 61

Slide 61 text

This implies a lot of test and code isolation Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 62

Slide 62 text

More isolation that many people are comfortable committing to up front Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 63

Slide 63 text

Test doubles are design canaries Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 64

Slide 64 text

Lot of setup might mean poorly factored design Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 65

Slide 65 text

This level of isolation is hard on a team if you are the only one who cares Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 66

Slide 66 text

A third-party framework can make this hard Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 67

Slide 67 text

So what happened? Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 68

Slide 68 text

I wrote the spy tests Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 69

Slide 69 text

I was unwilling or unable to make the code isolated enough to keep the spies Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 70

Slide 70 text

I re-wrote them Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 71

Slide 71 text

This is easy But it breaks encapsulation But it's easy Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 72

Slide 72 text

Takeaways Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 73

Slide 73 text

Conway's law applies to tests Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 74

Slide 74 text

Think about what will make your tests fail Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 75

Slide 75 text

Try doubles where behavior is more important than state Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 76

Slide 76 text

Try writing Rails tests with isolation Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 77

Slide 77 text

Using an integration test can force additional unit tests Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 78

Slide 78 text

Noel Rappin (@noelrap) (http://www.noelrappin.com) Table XI https://tinyletter.com/noelrap http://pragprog.com/book/nrwebpay http://pragprog.com/book/nrtest2 Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap

Slide 79

Slide 79 text

Noel Rappin | Test Doubles are not to be Mocked | http://www.noelrappin.com | http://www.tablexi.com | @noelrap