-
Notifications
You must be signed in to change notification settings - Fork 0
/
14.3.txt
50 lines (33 loc) · 4.61 KB
/
14.3.txt
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
14.3 Message Expectations
A message expectation, aka mock expectation, is a method stub that will raise an error if it is never called. In RSpec, we create a message expectation using the should_receive( ) method, like this:
describe Statement do
it "uses the customer's name in the header" do
customer = double('customer') customer.should_receive(:name).and_return('Aslak')
statement = Statement.new(customer)
statement.generate.should =~ /^Statement for Aslak/
end
end
Using should_receive( ) instead of stub( ) sets an expectation that the cus- tomer double should receive the name( ) message. The subsequent and_ return( ) works just like before: it is an instruction to return a specific value in response to name( ).
In this example, if the generate( ) method fails to ask the customer double for its name, then the example will fail with Double "customer" expected :name with (any args) once, but received it 0 times . If the generate( ) method calls customer.name(), then the customer double returns the programmed value, execution continues, and the example passes.
Tight Coupling
Clearly, this example is highly coupled to the implementation, but this coupling is easily justified. We are specifying that the statement uses the customer’s name! If that is the requirement that we are expressing in this example, then setting a message expectation is perfectly reasonable.
On the flip side, we want to avoid setting message expectations that are not meaningful in the context of an example. Generally speaking, we only want to use message expectations to express the intent of the example. To explain what we mean, let’s look at an example that uses both a method stub and a method expectation.
Mixing Method Stubs and Message Expectations
Extending the statement examples, let’s add a requirement that any time a statement is generated, a log entry gets created. Here’s one way we might express that:
describe Statement do
it "logs a message on generate()" do
customer = stub('customer')
customer.stub(:name).and_return('Aslak')
logger = mock('logger')
statement = Statement.new(customer, logger)
logger.should_receive(:log).with(/Statement generated for Aslak/)
statement.generate
end
end
Now we have three participants in the example: the statement, which is the subject of the example; the logger, which is the primary collaborator; and the customer, which is a secondary collaborator. The logger is the primary collaborator because the example’s docstring states that the Statement logs a message on generate().
By using the mock( ) method to generate the logger double and the stub( ) method to generate the customer double, we’re helping to express that these objects are playing different roles in the example. This is a won- derful technique, embedding intent right in the code in the example.
Given, Then, When?
The logger.should_receive() statement is the only expectation in the example, and it comes before the event, the When. The resulting flow is a bit different from the Given, When, Then flow that we’re accustomed to seeing. Here it’s Given, Then, When: Given a statement constructed with a customer and logger, Then the logger should receive log( ) When the statement receives generate( ).
This change in flow can be a bit jarring for those experienced in writing code examples yet new to message expectations, so much so that some in the community are beginning to solve the problem with new libraries that take different approaches. See the sidebar on the next page for more on this.
Of course, what we don’t see is that there is an automatic and implicit verification step that happens at the end of each example. This is facilitated by the test double framework hooking into the life cycle of the examples, listening for the end of each example, and then verifying that any expectations set in the example were met. So, the flow is really Given, Expect, When, Then, but since we never see the Then, it is admittedly a bit magical.
Thus far, we’ve only talked about setting method stubs and message expectations on test double objects. This is a very useful technique when the collaborators we need either don’t exist yet or are very expen- sive to set up or use in a code example. But sometimes the collaborator we need already exists, requires little or no setup, and exposes only triv- ial behavior. For cases like this, we can add support for method stubs and message expectations directly to the real object using a technique called test-specific extensions.