开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜