File: readme.md

package info (click to toggle)
ruby-bogus 0.1.5-3
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 828 kB
  • ctags: 628
  • sloc: ruby: 4,124; makefile: 6; sh: 2
file content (173 lines) | stat: -rw-r--r-- 7,252 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
Bogus is a library that aims to reduce the risks associated with isolated unit testing.  

## The problem

It is not uncommon to encounter code like this in isolated unit tests:

    it "returns the average score" do
      scores = stub(get: [5, 9])
      students = stub(all: ["John", "Mary"])

      report_card = ReportCard.new(scores, students)

      expect(report_card.average_score).to eq(7)
    end

_NOTE:  In the above example we use mocha syntax, but this patten is common
to all the major mocking frameworks_

The test above not only ensures that `ReportCard` can calculate the average score for all the students, but it also specifies how this class will interact with its collaborators and what those collaborators will be.

This style of testing enables us to practice so called *programming by wishful thinking*.  We can implement `ReportCard` and get it to work before it's collaborators are implemented.  This way we can design our system top-down and only implement what we need. This is a Very Good Thing(tm).

However, the same freedom that comes from not having to implement the collaborators, can quickly turn against us. Once implement `ReportCard`, what test will tell us that `Scores` and `Students` are not implemented yet?

The kind of stubbing that you see in the example above requires us to write integrated or end-to-end test *just to make sure that the objects we implemented fit together*.  This is a problem, because those tests can be quite slow and hard to set up in a way that covers all the integration points between our objects.

Another problem is that it's quite likely that `Students` and `Scores` will be collaborating with more objects than the `ReportCard`. If so, you will find yourself typing code like this over and over again:

    students = stub(all: ["John", "Mary"])

This is duplication. It is also a problem, because if you decide to change the interface of the `Students` class, suddenly you will have to remember every place you created that stub and fix it. Your tests won't help you, because they don't have any idea what the `Students` interface should look like.

## The solution

Bogus makes your test doubles more assertive. They will no longer be too shy to tell you: "Hey, you are stubbing the wrong thing!".

Let's reexamine our previous example, this time Bogus-style:

    it "returns the average score" do
      scores = fake(:scores, get: [5, 9])
      students = fake(:students, all: ["John", "Mary"])

      report_card = ReportCard.new(scores, students)

      expect(report_card.average_score).to eq(7)
    end

Can you spot the difference? Not much, huh?

However, the code above will not only make sure that the `ReportCard` works, it will also make sure that classes `Students` and `Scores` exist and have an interface that matches what you stub on them.

## DRY out your fake definitions

By now you know how Bogus can help you with making sure that your stubs match the interface of your production classes. However, the attentive reader has probably spotted a problem with the solution above already. If all you need to change in your tests is the word `stub` to `fake` and give it some name, then how is that any better when it comes to scattering the fake interface definition all over your specs?

The answer is: it's just slightly better. The reason it's better is that you only need to ever stub methods that return meaningful values. Let us explain.

### Tell-don't-ask methods

Let's say we have a `PushNotifier` class:

    class PushNotifier
      def notify_async(messages)
      end
    end

Now if you test an object that collaborates with our `PushNotifier`, you would do something like this:

    fake(:push_notifier)

    it "send push notifications when comment is added" do
      comment_adder = CommentAdder.new(push_notifier)

      comment_adder.add("Hello world!")

      expect(push_notifier).to have_received.notify_async("Comment: 'Hello world!' added.")
    end

While not really impressive, this feature is worth mentioning because it will eliminate a lot of the mocking and stubbing from your tests.

### Global fake configuration

Bogus also has a solution for DRYing stubbing of the methods that return values. All you need to do is to provide a reasonable default return value for those methods in the global fake configuration.

      Bogus.fakes do
        fake(:students) do
          all []
        end

        fake(:scores) do
          get []
        end
      end

Now you will only need to stub those methods when you actually care about their return value, which is exactly what we want.

## Contract tests

Bogus is not the only mocking library to implement fakes and safe mocking. However, it is the first library to implement the concept of contract tests [as defined by J. B. Rainsberger][contracts].

Let's start with an example:

    stub(scores).get(["John", "Mary"]) { [5, 9] }

We already know that Bogus makes sure for us that the `Scores` class exists, has a `get` method and that this method can be called with one argument.

It would also be nice to know that the input/output that we stub makes sense for this method.

Contract tests are an idea, that whenever we stub `Scores#get` with argument `["John", "Mary"]` to return the value `[5, 9]`, we should add a test for the Scores class, that calls method `get` with those same arguments and have the same return value.

A contract test like that could look like this:

    it "returns scrores for students" do
      scores = Scores.new(redis)
      scores.add("John", 5)
      scores.add("Mary", 9)

      expect(scores.get(["John", "Mary"])).to eq([5, 9])
    end

Obviously Bogus won't be able to write those tests for you. However it can remind you if you forget to add one.

To add contract test verification, the only thing you need to do is add the line:

    verify_contract(:students)

to your tests for `Students` class.

## Not only for people who use Dependency Injection

The examples above all have one thing in common: they assume that your code has some way of injecting dependencies. We believe it should, and that's why we wrote [Dependor][dependor].

However, we are aware that this practice is not very common in the Ruby community. That's why Bogus supports replacing classes with fakes.

Let's assume, that you have production code like this:

    class PushNotifier
      def self.notify_async(message)
        # ...
      end
    end

    class CommentAdder
      def self.add(parent, comment)
        PushNotifier.notify_async("comment added")
        # ...
      end
    end

You can test it easily, with all the benefits of fakes, safe stubbing and contracts:

    describe CommentAdder do
      fake_class(PushNotifier)

      it "should send a push notification" do
        CommentAdder.add("the user", "the comment")

        expect(PushNotifier).to have_received.notify_async("comment added")
      end
    end

    describe PushNotifier do
      verify_contract(:push_notifier)

      it "notifies about comments asynchronously" do
        PushNotifier.notify_async("comment added")

        # ...
      end
    end

[contracts]: http://www.infoq.com/presentations/integration-tests-scam
[dependor]: https://github.com/psyho/dependor