RSpec模拟失败,"未定义方法` - '为nil:NilClass"

J.P*_*.P. 2 ruby unit-testing rspec rspec2

我有一个简单的方法,我想用RSpec测试.我想确保apply减少player.capacity一个.为此,我已经模拟了一个玩家对象并正在测试它是否收到正确的消息.

class DecreaseCapacity < Item
  def apply player
    player.capacity -= 1
  end
end
Run Code Online (Sandbox Code Playgroud)

测试

describe DecreaseCapacity, "#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
Run Code Online (Sandbox Code Playgroud)

失败消息

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
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?为什么player.capacity -= 1试图调用-nil

The*_*heo 10

问题是,当你capacity调用播放器的方式时,它会在调用时返回nil .你需要这样改变:

player.should_receive(:capacity).and_return(0)
player.should_receive(:capacity=).with(1)
Run Code Online (Sandbox Code Playgroud)

要理解原因,让我们分解代码中发生的事情.如果我们扩展-=为:将更容易看到问题:

player.capacity = player.capacity - 1
Run Code Online (Sandbox Code Playgroud)

使用存根播放器,它变为:

player.capacity = nil - 1
Run Code Online (Sandbox Code Playgroud)

这正是RSpec所抱怨的.

现在,让我建议一个更好的方法来编写测试.您的测试只是镜像您的实现,它不测试方法.我的意思是它没有测试该apply方法将玩家的容量增加一个 - 它测试该apply调用capacity然后capacity=.您可能认为这是一回事,但这只是因为您知道如何实施该方法.

这就是我写测试的方式:

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
Run Code Online (Sandbox Code Playgroud)

我使用了一个真实的Player对象,而不是设置一个存根,因为我认为实现Player#capacity只是一个访问器,没有逻辑干扰我的测试.使用存根的风险是,有时存根比真实对象更复杂(在这种情况下,我会争辩),这意味着你的测试更可能比你的实际代码错误.

如果你想使用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
Run Code Online (Sandbox Code Playgroud)