问题
I have a method depart(plane)
which takes an error fail "The plane can't set off because it is stormy" if @weather.stormy?
. This is defined in my airport class
class Airport
attr_accessor :planes, :landed, :weather
def initialize(weather = Weather.new)
#plane has no location when initialized
@landed = nil
@planes = []
@weather = weather
end
def land(plane)
fail "You can't land this plane again!" if @landed == true
@planes << plane
@landed = true
end
def depart(plane)
fail "The plane has already departed" if @landed == false
fail "The plane can't set off because it is stormy" if @weather.stormy?
@planes.pop
puts "Your plane has left the airport!"
@landed = false
end
end
I also have a plane class:
class Plane
end
The method .stormy?
is a method in which a random number is generated. If the number is above 75, a storm is generated otherwise it is false. This is defined in my weather class
def stormy?
number > 70 ? true : false
end
def number
rand(1..100)
end
I am trying to test the error fail "The plane can't set off because it is stormy" if @weather.stormy?
using RSpec. I am finding this extremely difficult as I am very new to RSpec.
The problem I am having is passing this test as the storm either can be true or false. How can I preset a value and test it?
My whole airport_spec.rb file:
require 'airport'
require 'plane'
require 'weather'
describe Airport do
let(:airport) { Airport.new }
let(:plane) { double :plane }
let(:weather) { double :weather}
#let(:weather) {double :weather}
it 'creates new airports' do
expect(:airport).to eq(:airport)
end
it 'accepts landed planes' do
subject.land(:plane)
expect(subject.landed).to be(true)
end
describe '#initialize' do
it 'initializes a planes array when airport is instantiated' do
expect(airport.planes).to be_an_instance_of(Array)
end
it 'initializes the plane to be landed to be nil upon instantiation' do
expect(airport.landed).to be nil
end
it 'instantiates a new weather object upon initialization' do
weather = airport.weather
expect(airport.weather).to eq weather
end
end
describe '#land' do
it 'adds a plane to the planes array when landed' do
subject.land(:plane)
expect(subject.planes).to eq [:plane]
end
it 'will not land a plane that is already landed' do
subject.land(:plane)
expect {subject.land(:plane)}.to raise_error("You can't land this plane again!")
end
end
describe '#depart' do
it 'will not allow a plane to take off when it is stormy' do
weather = Weather.new
allow(weather).to receive(:stormy?).and_return true
expect{subject.depart(plane)}.to raise_error("The plane can't set off because it is stormy")
end
end
describe '#full' do
it 'will raise an error when the airport is too full' do
expect(subject.full?).to eq(true)
end
end
end
The test that is failing:
it 'will not allow a plane to take off when it is stormy' do
weather = Weather.new
allow(Airport.new).to receive(weather.stormy?).and_return true
expect{subject.depart(plane)}.to raise_error("The plane can't set off because it is stormy")
end
end
Why am I getting:
Failures:
1) Airport#depart will not allow a plane to take off when it is stormy
Failure/Error: expect{subject.depart(plane)}.to raise_error("The plane can't set off because it is stormy")
expected Exception with "The plane can't set off because it is stormy" but nothing was raised
# ./spec/airport_spec.rb:75:in `block (3 levels) in <top (required)>'
Finished in 0.02878 seconds (files took 0.15908 seconds to load)
12 examples, 1 failure
RSpec version : 3.5.4
Another time I run:
Failures:
1) Airport#depart will not allow a plane to take off when it is stormy
Failure/Error: expect{subject.depart(plane)}.to raise_error("The plane can't set off because it is stormy")
expected Exception with "The plane can't set off because it is stormy" but nothing was raised
# ./spec/airport_spec.rb:58:in `block (3 levels) in <top (required)>'
2) Airport#depart removes a plane from the planes array when taken-off
Failure/Error: fail "The plane can't set off because it is stormy" if @weather.stormy?
RuntimeError:
The plane can't set off because it is stormy
# ./lib/airport.rb:20:in `depart'
# ./spec/airport_spec.rb:63:in `block (3 levels) in <top (required)>'
Finished in 0.03361 seconds (files took 0.15734 seconds to load)
16 examples, 2 failures
Failed examples:
rspec ./spec/airport_spec.rb:56 # Airport#depart will not allow a plane to take off when it is stormy
rspec ./spec/airport_spec.rb:61 # Airport#depart removes a plane from the planes array when taken-off
However, sometimes it works and passes:
COVERAGE: 100.00% -- 76/76 lines in 6 files
Benjamins-MacBook-Pro:airport_challenge benjamin$ rspec
Airport
creates new airports
accepts landed planes
#initialize
initializes a planes array when airport is instantiated
initializes the plane to be landed to be nil upon instantiation
instantiates a new weather object upon initialization
#land
adds a plane to the planes array when landed
will not land a plane that is already landed
#depart
will not allow a plane to take off when it is stormy
#full
will raise an error when the airport is too full
Plane
creates new planes
Weather
creates a weather object
#number
will create a random number
#storm
will either be stormy or sunny
Have you considered running rubocop? It will help you improve your code!
Try it now! Just run: rubocop
Finished in 0.01254 seconds (files took 0.15439 seconds to load)
13 examples, 0 failures
COVERAGE: 96.05% -- 73/76 lines in 6 files
+----------+----------------+-------+--------+---------+
| coverage | file | lines | missed | missing |
+----------+----------------+-------+--------+---------+
| 83.33% | lib/airport.rb | 18 | 3 | 25-27 |
+----------+----------------+-------+--------+---------+
5 file(s) with 100% coverage not shown
回答1:
When you say allow(Airport.new).to receive...
this is setting up a new Airport
, not referring to the one you've set up using let
.
The stormy?
method is on Weather
so the expectation should be:
allow(weather).to receive(:stormy?).and_return true
and set up the test subject to use the weather with the mocked stormy?
method:
let(:weather) { double :weather }
subject { Airport.new(weather) }
As a side explanation, receive
takes the name of a method that will be called. When you write receive(weather.stormy?)
RSpec is trying to convert the return value from weather.stormy?
(which would be True
or False
) into a symbol to use as the method name, hence undefined method
to_sym'`.
Looking at the full version of your spec, you are currently an implicit subject i.e. based on the describe Airport...
RSpec has created an Airport
using Airport.new
(with no arguments) to be the subject of your spec. This means that the subject isn't using the weather
object where you've stubbed the stormy?
method and so your real stormy?
method is still being used. This is why I set up an explicit subject using subject { Airport.new(weather) }
. Lastly, you don't want to be setting up a local weather
in the test (weather = Weather.new
) because you want allow(weather)...
to refer to the weather from the let
that's passed to the test subject.
Here's a complete version of the spec with the changes made:
require 'airport'
describe Airport do
let(:weather) { double :weather }
subject { Airport.new(weather) }
let(:plane) { double :plane }
it 'will not allow a plane to take off when it is stormy' do
allow(weather).to receive(:stormy?).and_return true
expect { subject.depart(plane) }.to raise_error("The plane can't set off because it is stormy")
end
end
来源:https://stackoverflow.com/questions/46026166/exception-exception-rspec