I want to test the functionality of the method using rspec that receives anonymous block and not raise error. Below is my code:
class SP
def speak(options={},&block)
puts "speak called"
block.call()
rescue StandardError => e
puts e.inspect()
end
end
describe SP do
it "testing speak functionality not to raise error" do
sp = SP.new
sp_mock = double(sp)
expect(sp_mock).to receive(:speak).with(sp.speak{raise StandardError}).not_to raise_error
end
end
It is below throwing error
SP testing speak functionality not to raise error
Failure/Error: expect(sp).to receive(:speak).with(sp.speak{raise StandardError})
(#<SP:0x007fead2081d20>).speak(nil)
expected: 1 time with arguments: (nil)
received: 0 times
# ./test.rb:22:in `block (2 levels) in <top (required)>'
Spent a lot of time browsing articles of ruby blocks and ruby documentation but can't figure out.
It's too complicated for no reason. Did you mean this?
it "testing speak functionality not to raise error" do
sp = SP.new
expect {
sp.speak {raise StandardError}
}.to_not raise_error
end
Related
I want to check whether the block is called in my function using rspec. Below is my code:
class SP
def speak(options={},&block)
puts "speak called"
block.call()
rescue ZeroDivisionError => e
end
end
describe SP do
it "testing speak functionality can receive a block" do
sp = SP.new
def test_func
a = 1
end
sp_mock = double(sp)
expect(sp_mock).to receive(:speak).with(test_func)
sp.speak(test_func)
end
end
Below is my error:
SP testing speak functionality can receive a block
Failure/Error: block.call()
NoMethodError:
undefined method `call' for nil:NilClass
# ./test.rb:9:in `speak'
# ./test.rb:25:in `block (2 levels) in <top (required)>'
Could you please help. I spent lots of time in that.
You have to use one of RSpec's yield matcher:
describe SP do
it "testing speak functionality can receive a block" do
sp = SP.new
expect { |b| sp.speak(&b) }.to yield_control
end
end
I think Stefan provided the best answer. However I wanted to point out that you should be testing the behaviour of the code instead of implementation details.
describe SP do
it "testing speak functionality can receive a block" do
sp = SP.new
called = false
test_func = -> () { called = true }
sp.speak(&test_func)
expect(called).to eql(true)
end
end
This is something that I've seen before when using RSpec Rails and I believe that I know what is happening, I just don't know how I can get around it.
To me, it appears that the following test should pass. It expects an error, and an error is raised although I assume that the source of the error is what it is tripping up on.
csv_file_spec.rb
require 'spec_helper'
RSpec.describe Cleaner::CSVFile do
context 'when CSV file does not exist' do
let(:file) { Cleaner::CSVFile.new('tmp/file-does-not-exist.csv') }
it 'raises error' do
expect(file).to raise_error
end
end
end
csv_file.rb
module Cleaner
# A CSVFile is a CSV file loaded into memory. It exposes the clean method.
class CSVFile
attr_accessor :raw
def initialize(file)
#raw = File.open(file)
end
end
end
Output
1) Cleaner::CSVFile is not valid
Failure/Error: expect(Cleaner::CSVFile.new('tmp/file-does-not-exist.csv')).to raise_error
Errno::ENOENT:
No such file or directory # rb_sysopen - tmp/file-does-not-exist.csv
# ./lib/cleaner/csv_file.rb:8:in `initialize'
# ./lib/cleaner/csv_file.rb:8:in `open'
# ./lib/cleaner/csv_file.rb:8:in `initialize'
# ./spec/csv_file_spec.rb:7:in `new'
# ./spec/csv_file_spec.rb:7:in `block (2 levels) in <top (required)>'
I can see that the CSVFile object is not able to be initialized because the file does not exist and that'll be why RSpesc can't continue the test but what can I do to get around this?
I get the feeling that there is something fundamentally wrong with my approach to testing that I'm not seeing. I'd rather delegate the error to the standard File class, and not raise my own error messages as the error is verbose enough and I'd only be duplicating effort - should I be implementing my own instead?
Thanks!
For exceptions you should use block or lambda in expect syntax:
it 'raises error' do
expect{ Cleaner::CSVFile.new('tmp/file-not-exist.csv') }.to raise_error
end
You could use stubbing also :
require 'spec_helper'
RSpec.describe Cleaner::CSVFile do
context 'when CSV file does not exist' do
it 'raises error' do
allow(described_class).to receive(:new).and_raise("File not exist")
expect { described_class.new }.to raise_error("File not exist")
end
end
end
Read match message with a string.
I have an rspec test on a pure Ruby model:
require 'spec_helper'
require 'organization'
describe Organization do
context '#is_root?' do
it "creates a root organization" do
org = Organization.new
expect { org.is_root?.to eq true }
end
end
end
My organization model looks like this:
class Organization
attr_accessor :parent
def initialize(parent = nil)
self.parent = parent
end
end
The output when running the tests:
bundle exec rspec spec/organization_spec.rb:6
Run options: include {:locations=>{"./spec/organization_spec.rb"=>[6]}}
.
Finished in 0.00051 seconds
1 example, 0 failures
When I run the test, it passes, despite the fact that the method is_root? doesn't exist on the model. I usually work in Rails, not pure Ruby, and I've never seen this happen. What is going on?
Thanks!
It should be:
expect(org.is_root?).to eq true
When you pass block to expect it is being wrapped in ExpectationTarget class (strictly speaking BlockExpectationTarget < ExpectationTarget). Since you didn't specify what you expect from this object, the block is never executed, hence no error is raised.
You are passing a block to expect, which is never being called. You can see this by setting an expectation on that block
expect { org.is_root?.to eq true }.to_not raise_error
1) Organization#is_root? creates a root organization
Failure/Error: expect { puts "HI";org.is_root?.to eq true }.to_not raise_error
expected no Exception, got #<NoMethodError: undefined method `is_root?' for #<Organization:0x007ffa798c2ed8 #parent=nil>> with backtrace:
# ./test_spec.rb:15:in `block (4 levels) in <top (required)>'
# ./test_spec.rb:15:in `block (3 levels) in <top (required)>'
# ./test_spec.rb:15:in `block (3 levels) in <top (required)>'
Or by just putting a plain raise or puts inside the block, neither of which will be called:
expect { puts "HI"; raise; org.is_root?.to eq true }
The block form is used for expecting that a piece of code raises an exception or not. The correct syntax for checking values is:
expect(org.is_root?).to eq(true)
Just learning rspec syntax and I noticed that this code works:
context "given a bad list of players" do
let(:bad_players) { {} }
it "fails to create given a bad player list" do
expect{ Team.new("Random", bad_players) }.to raise_error
end
end
But this code doesn't:
context "given a bad list of players" do
let(:bad_players) { {} }
it "fails to create given a bad player list" do
expect( Team.new("Random", bad_players) ).to raise_error
end
end
It gives me this error:
Team given a bad list of players fails to create given a bad player list
Failure/Error: expect( Team.new("Random", bad_players) ).to raise_error
Exception:
Exception
# ./lib/team.rb:6:in `initialize'
# ./spec/team_spec.rb:23:in `new'
# ./spec/team_spec.rb:23:in `block (3 levels) in <top (required)>'
My question is:
Why does this happen?
What is the difference between the former and later example exactly in ruby?
I am also looking for rules on when to use one over the other
One more example of the same but inverse results, where this code works:
it "has a list of players" do
expect(Team.new("Random").players).to be_kind_of Array
end
But this code fails
it "has a list of players" do
expect{ Team.new("Random").players }.to be_kind_of Array
end
Error I get in this case is:
Failure/Error: expect{ Team.new("Random").players }.to be_kind_of Array
expected #<Proc:0x007fbbbab29580#/Users/amiterandole/Documents/current/ruby_sandbox/tdd-ruby/spec/team_spec.rb:9> to be a kind of Array
# ./spec/team_spec.rb:9:in `block (2 levels) in <top (required)>'
The class I am testing looks like this:
class Team
attr_reader :name, :players
def initialize(name, players = [])
raise Exception unless players.is_a? Array
#name = name
#players = players
end
end
As has been mentioned:
expect(4).to eq(4)
This is specifically testing the value that you've sent in as the parameter to the method. When you're trying to test for raised errors when you do the same thing:
expect(raise "fail!").to raise_error
Your argument is evaluated immediately and that exception will be thrown and your test will blow up right there.
However, when you use a block (and this is basic ruby), the block contents isn't executed immediately - it's execution is determined by the method you're calling (in this case, the expect method handles when to execute your block):
expect{raise "fail!"}.to raise_error
We can look at an example method that might handle this behavior:
def expect(val=nil)
if block_given?
begin
yield
rescue
puts "Your block raised an error!"
end
else
puts "The value under test is #{val}"
end
end
You can see here that it's the expect method that is manually rescuing your error so that it can test whether or not errors are raised, etc. yield is a ruby method's way of executing whatever block was passed to the method.
In the first case, when you pass a block to expect, the execution of the block doesn't occur until it's time to evaluate the result, at which point the RSpec code can catch any error that are raised and check it against the expectation.
In the second case, the error is raised when the argument to expect is evaluated, so the expect code has no chance to get involved.
As for rules, you pass a block or a Proc if you're trying to test behavior (e.g. raising errors, changing some value). Otherwise, you pass a "conventional" argument, in which case the value of that argument is what is tested.
I'm having problems getting this simple test to pass on RSpec 2.8.
I want to write a simple test for the absence of parameters on a method that requires them (i.e. ArgumentError: wrong number of arguments ('x' for 'y')).
My test is testing a Gem module method like so:
describe "#ip_lookup" do
it "should raise an ArgumentError error if no parameters passed" do
expect(#geolocater.ip_lookup).to raise_error(ArgumentError)
end
end
My gem module code looks like this:
module Geolocater
def ip_lookup(ip_address)
return ip_address
end
end
My spec runs with this output.
Failure/Error: expect(#geolocater.ip_lookup).to raise_error(ArgumentError)
ArgumentError:
wrong number of arguments (0 for 1)
# ./lib/geolocater.rb:4:in `ip_lookup'
# ./spec/geolocater_spec.rb:28:in `block (3 levels) in <top (required)>'
What am I missing here?
You need to pass a block to #expect, not a regular argument:
describe "#ip_lookup" do
it "should raise an ArgumentError error if no parameters passed" do
expect { #geolocater.ip_lookup }.to raise_error(ArgumentError)
end
end