Макет RSpec завершается с ошибкой с неопределенным методом `-' для nil:NilClass

У меня есть простой метод, который я хочу протестировать с помощью RSpec. Я хочу убедиться, что apply уменьшает player.capacity на единицу. Для этого я издевался над объектом игрока и проверяю, получает ли он правильные сообщения.

Код

class DecreaseCapacity < Item
  def apply player
    player.capacity -= 1
  end
end

Тестовое задание

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

Сообщение об ошибке

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

Что тут происходит? Почему player.capacity -= 1 пытается позвонить - на nil?


person J.P.    schedule 22.12.2010    source источник


Ответы (1)


Проблема в том, что способ, которым вы заглушили игрока, вернет nil при вызове capacity. Вам нужно изменить так:

player.should_receive(:capacity).and_return(0)
player.should_receive(:capacity=).with(1)

Чтобы понять почему, давайте разберем, что происходит в вашем коде. Будет легче увидеть проблему, если мы расширим -= до:

player.capacity = player.capacity - 1

С вашим заглушенным плеером это становится так:

player.capacity = nil - 1

именно на это жалуется 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

Я использовал настоящий объект 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
person Theo    schedule 22.12.2010