RSpec mock fails with "undefined method `-' for nil:NilClass"
I have a simple method that I want to test with RSpec. I want to ensure that apply
decrements player.capacity
by one. To do this, I have mocked a player object and am testing to see if it receives the correct messages.
Code
class DecreaseCapacity < Item
def apply player
player.capacity -= 1
end
end
Test
describe DecreaseCapacit开发者_运维问答y, "#apply" do
it "should decrease capacity by one" do
player = double()
player.should_receive(:capacity) # reads the capacity
player.should_receive(:capacity=) # decrement by one
subject.apply player
end
end
Failure Message
1) DecreaseCapacity#apply should decrease the player's capacity by one
Failure/Error: subject.apply player
undefined method `-' for nil:NilClass
# ./item.rb:39:in `apply'
# ./item_spec.rb:25
What's going on here? Why is player.capacity -= 1
attempting to call -
on nil
?
The problem is that the way you've stubbed the player it will return nil when capacity
is called. You need to change like so:
player.should_receive(:capacity).and_return(0)
player.should_receive(:capacity=).with(1)
To understand why, let's break down what happens in your code. It will be easier to see the problem if we expand the -=
to:
player.capacity = player.capacity - 1
With your stubbed player it becomes this:
player.capacity = nil - 1
which is exactly what RSpec complains about.
Now, let me suggest a better way to write the test. Your test simply mirrors your implementation, it does not test the method. What I mean by that is that it doesn't test that the apply
method increments a player's capacity by one -- it tests that apply
calls capacity
and then capacity=
. You may think it's the same thing, but that's only because you know how you have implemented the method.
This is how I would have written the test:
it "increments a player's capacity" do
player = Player.new # notice that I use a real Player
player.capacity = 0
subject.apply(player)
player.capacity.should == 1
end
I used a real Player
object instead of setting up a stub, because I assume that the implementation of Player#capacity
is just an accessor, there's no logic in there to interfere with my test. The risk with using stubs is that sometimes the stubs get even more complicated than the real objects (as in this case, I would argue), and that means that it's more likely that your test is wrong than your actual code.
You can also write the test like this, if you want to use the full expressiveness of RSpec:
it "increments a player's capacity" do
player = Player.new
player.capacity = 0
expect { subject.apply(player) }.to change { player.capacity }.to(1)
end
精彩评论