I'm new to learning RSpec. I can't seem to understand why my test #start method
is failing.
would be grateful if one could explain to me.
the error i am getting:
CardGame
attributes
should have a name
#response method
should say hello
#start method
can only implement class methods that are defined on a class (FAILED - 1)
Failures:
1) CardGame#start method can only implement class methods that are defined on a class
Failure/Error:
def initialize(name)
@name = name
end
ArgumentError:
wrong number of arguments (given 0, expected 1)
# ./lib/CardGame.rb:4:in `initialize'
# ./spec/class_double_spec.rb:29:in `block (3 levels) in <top (required)>'
Finished in 0.01178 seconds (files took 0.30252 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./spec/class_double_spec.rb:27 # CardGame#start method can only implement class methods that are defined on a class
➜ rspec-course
class_double_spec.rb
[ruby/spec/class_double_spec.rb]
require 'spec_helper'
require 'pry'
require './lib/CardGame'
require './lib/Deck'
describe CardGame do
let(:card) { instance_double(CardGame,
name: 'poker',
response: 'hello')}
let(:deck_klass) { class_double(Deck, build: ['Ace', 'Queen']).as_stubbed_const }
context 'attributes' do
it 'should have a name' do
expect(card.name).to eq('poker')
end
end
context '#response method' do
it 'should say hello' do
allow(card).to receive(:response).and_return('hello')
expect(card.response).to eq('hello')
end
end
context '#start method' do
it 'can only implement class methods that are defined on a class' do
expect(deck_klass).to receive(:build)
card.start
expect(card.cards).to eq(['Ace', 'Queen'])
end
end
end
CardGame.rb
[ruby/lib/CardGame.rb]
class CardGame
attr_accessor :name, :cards
def initialize(name)
@name = name
end
def response
'hello'
end
def start
@cards = Deck.build
end
end
Deck.rb
[ruby/lib/Deck.rb]
class Deck
def self.build
# business logic to build cards
end
end
You mock to mock. One of the rules one should strictly apply is not to mock unit under test, so instead of
describe CardGame do
let(:card) { instance_double(CardGame,
name: 'poker',
response: 'hello')}
let(:deck_klass) { class_double(Deck, build: ['Ace', 'Queen']).as_stubbed_const }
context 'attributes' do
it 'should have a name' do
expect(card.name).to eq('poker')
end
end
end
do this
describe CardGame do
let(:card) { CardGame.new(name) }
let(:name) { 'poker'}
context 'attributes' do
it 'should have a name' do
expect(card.name).to eq('poker')
end
end
end
Why should you not mock the unit under test?
context '#response method' do
it 'should say hello' do
allow(card).to receive(:response).and_return('hello')
expect(card.response).to eq('hello')
end
end
Because in the example you're only testing that the RSpec's mocking framework works. Simplify it
context '#response method' do
it 'should say hello' do
expect(card.response).to eq('hello')
end
end
The last example:
context '#start method' do
it 'can only implement class methods that are defined on a class' do
expect(deck_klass).to receive(:build)
card.start
expect(card.cards).to eq(['Ace', 'Queen'])
end
end
Looks better, the mocked class is not directly tested, but used by the unit under test ( Card
).
If you're learning RSpec - try to mock as little as possible. This will force you to design your classes so they're easy to test. Right now you're designing it in your old habitual way, that makes the class hard to test, which forces you to use mocks (and since you're just learning - it's easy to not spot the placec where you don't test anything)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.