I have a problem with mocking. I have class DistanceMatrix and I would
like to indicate which method form_matrix was called in if/else
statement. I need to use mocha and RSpec. Any ideas?
class DistanceMatrix
def initialize(*args)
if args[0].class == String
form_matrix(get_data_from_yaml(args[0], args[1]))
elsif args[0].class == Array || args[0] == nil
form_matrix(get_data_from_db(args[0]))
end
end
def form_matrix(...)
...
end
end
it tried:
describe DistanceMatrix, "when mocking ..." do
it "should do call form_matrix" do
DistanceMatrix.any_instance.expects(:form_matrix).with([1]).once
DistanceMatrix.any_instance.expects(:get_data_from_yaml).with("file_name.yml").once.returns([1])
DistanceMatrix.new("file_name.yml")
end
end
but got error:
Failures:
1) DistanceMatrix when mocking ... should do call form_matrix
Failure/Error: DistanceMatrix.new("file_name.yml")
unexpected invocation: #<AnyInstance:DistanceMatrix>.get_data_from_yaml('file_name.yml', nil)
unsatisfied expectations:
- expected exactly once, not yet invoked: #<AnyInstance:DistanceMatrix>.get_data_from_yaml('file_name.yml')
- expected exactly once, not yet invoked: #<AnyInstance:DistanceMatrix>.form_matrix([1])
satisfied expectations:
- allowed any number of times, already invoked once: #<DistanceMatrix:0x9e48b40>.get_optimal_route(any_parameters)
- allowed any number of times, already invoked once: #<Database::Distances:0x9d59798>.load_distances(any_parameters)
# ./distance_matrix.rb:18:in `initialize'
# ./tsp_algorithm_spec.rb:253:in `new'
# ./tsp_algorithm_spec.rb:253:in `block (2 levels) in <top (required)>'
Finished in 0.25979 seconds
I found that in RSpec we should use not .expects() but .should_receive(), so I tried:
describe DistanceMatrix, "when mocking ..." do
it "should do call form_matrix" do
DistanceMatrix.any_instance.should_receive(:form_matrix).with([1])
DistanceMatrix.any_instance.should_receive(:get_data_from_yaml).with("file_name.yml").and_return([1])
DistanceMatrix.new("file_name.yml")
end
end
but got new failure:
Failures:
1) DistanceMatrix when mocking ... should do call form_matrix
Failure/Error: DistanceMatrix.any_instance.should_receive(:form_matrix).with([1])
(#<Mocha::ClassMethods::AnyInstance:0x96356b0>).form_matrix([1])
expected: 1 time
received: 0 times
# ./tsp_algorithm_spec.rb:251:in `block (2 levels) in <top (required)>'
Finished in 0.26741 seconds
I only have experience with using Mocha and not RSpec, but looking at the Mocha failure message, the key parts are these :-
unexpected invocation: #<AnyInstance:DistanceMatrix>.get_data_from_yaml('file_name.yml', nil)
unsatisfied expectations:
- expected exactly once, not yet invoked: #<AnyInstance:DistanceMatrix>.get_data_from_yaml('file_name.yml')
If you look at the ends of these lines, you will notice that get_data_from_yaml is not being called with the expected parameters. It is being called with ('filename.yml', nil) and not ('filename.yml') as expected.
This is happening because when you call DistanceMatrix.new("file_name.yml") in your test with only one argument and then inside DistanceMatrix#initialize DistanceMatrix#get_data_from_yaml is being called with (args[0], args[1]) and since args is a single element array, args[1] will be nil.
Maybe this isn't how you expected Ruby to work, but the following demonstrates this behaviour :-
def foo(*args)
puts "args[0]=#{args[0].inspect}; args[1]=#{args[1].inspect}"
end
foo("string") # => args[0]="string"; args[1]=nil
DistanceMatrix.any_instance.expects(:form_matrix).with("String") # => supply the correct string param
or
DistanceMatrix.any_instance.expects(:form_matrix).with([]) # => supply the correct array param
I'm not sure what your get_data_from_db and get_data_from_yaml methods are doing, but you should be able to control those inputs as well to verify the correct arguments are being supplied to form_matrix.
EDITED
You'll have to use DistanceMatrix.any_instance instead of mocking on an instance variable because you're trying to mock something in the initializer. Also, in case its unclear, you'll need to actually make the appropriate method call after you set up the mock in the lines above, e.g.
DistanceMatrix.new("SomeString")
EDITED
it "should do call #form_matrix with proper arguments" do
DistanceMatrix.any_instance.expects(:form_matrix).with([1])
DistanceMatrix.any_instance.expects(:get_data_from_yaml).with("foo").returns([1])
DistanceMatrix.new("foo")
end
Related
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)
Here is the class that I'm testing contained in Foo.rb:
class Foo
def bar
return 2
end
end
Here is the my test contained in Foo_spec.rb:
require "./Foo.rb"
describe "Foo" do
before(:all) do
puts "#{Foo == nil}"
Foo.any_instance.stub(:bar).and_return(1)
end
it "should pass this" do
f = Foo.new
f.bar.should eq 1
end
end
I am getting the following output:
false
F
Failures:
1) Foo Should pass this
Failure/Error: Foo.any_instance.stub(:bar).and_return(1)
NoMethodError:
undefined method `any_instance_recorder_for' for nil:NilClass
# ./Foo_spec.rb:6:in `block (2 levels) in <top (required)>'
Finished in 0 seconds
1 example, 1 failure
Failed examples:
rspec ./Foo_spec.rb:9 # Foo Should pass this
I've consulted the doc and the example given is passing on my machine (so it isn't a problem with the rspec code), but it isn't giving me any information on what I might be doing wrong. The error message is also quite confusing as it's telling me not to call .any_instance on a nil:NilClass, but as I proved with my output, Foo isn't nil. How am I supposed to call .any_instance.stub on my custom object?
I'm using Ruby 1.9.3 and rspec 2.14.5.
You should use before(:each) for stubbing.
Stubs in before(:all) are not supported. The reason is that all stubs and mocks get cleared out after each example, so any stub that is set in before(:all) would work in the first example that happens to run in that group, but not for any others.
rspec-mocks readme
From Rspec 3 any_instance is not defined anymore.
Now use:
allow_any_instance_of(Foo).to receive(:bar).and_return(1)
Source for this and older versions:
https://makandracards.com/makandra/2561-stub-methods-on-any-instance-of-a-class-in-rspec-1-and-rspec-2
Updating rspec worked for me. You can do it using the following command:
bundle update rspec
Quick rspec question here.
Given the following test...
it 'sets the correct params' do
property = 'blahblah'
expect(ConnectionClass).to receive(:new).with({ opt1: param1, opt2: property })
subject.send(:do_things, nil, nil).and_return_nil
end
and the following method...
private
def do_things(param1, param2)
connection = ConnectionClass.new(opt1: param1, opt2: param2)
##
# How do i stop the test from continue beyond this point? :(
##
some_var = connection.build_request do |blah|
# ...
end
some_var.some_attribute
end
Running the test results in the following failure:
Failures:
1) Klass#do_things sets the correct params
Failure/Error: subject.send(:do_things, nil, nil).and_return_nil
NoMethodError:
undefined method `build_request' for nil:NilClass
# ./lib/blah/Klass.rb:46:in `make_request'
# ./spec/lib/blah/Klass_spec.rb:79:in `block (3 levels) in <top (required)>'
For this test, all i care about is that ConnectionClass is initialized correctly -- how can i prevent the call to build_request and eventual some_attribute?
Thank you
This is generally a pretty good signal that you either need to refactor your code, or you are testing things that you need to not be testing. Rather than testing the implementation ("This class is instantiated with these parameters") consider testing your outputs (For input X, do_things should return output Y and have side effects Z). If you think "I want this test to stop in the middle of this method", you need to refactor the method you're testing so that you can discretely test just the bits you want to test.
That said, if you don't want to change your approach here, you could just return a double from your stub, so that it can complete the method.
some_var = double(some_attribute: "value")
connection = double(build_request: some_var)
expect(ConnectionClass).to receive(:new).with({ opt1: param1, opt2: property }).
and_return(connection)
File with object under test: foo.rb
class Foo
def a_string
"abcdef8"
end
end
Spec file: foo_spec.rb
require_relative "./foo"
describe Foo do
let(:foo) {Foo.new}
let(:my_matcher) {/^[a-z]+(\d)$/}
# This test passes
it "should match and pass" do
my_str = foo.a_string
my_matcher # <--- why does this affect the test?
matcher = my_str.match(my_matcher)
8.should == matcher[1].to_i
end
# This test fails
it "should also match and pass but fails" do
my_str = foo.a_string
#my_matcher #<---- the only change between the tests
matcher = my_str.match(my_matcher) #<---- when called here, it behaves differently
8.should == matcher[1].to_i
end
end
rspec foo_spec.rb
.F
Failures:
1) Foo should also match and pass but fails
Failure/Error: 8.should == matcher[1].to_i
NoMethodError:
undefined method `[]' for /^[a-z]+(\d)$/:Regexp
# ./foo_spec.rb:18:in `block (2 levels) in <top (required)>'
Finished in 0.00095 seconds
2 examples, 1 failure
Failed examples:
rspec ./foo_spec.rb:14 # Foo should also match and pass but fails
The only difference in the two tests is whether my_matcher is invoked. I first noticed this problem when I was inspecting my_matcher (i.e. p my_matcher), but it also occurs with just invoking my_matcher.
Is this a bug, or am I doing something wrong? Maybe it has something to do with capturing Regex data?
It seems incredibly odd behavior, especially for ruby.
For what its worth, it's an easy (if slightly less DRY) fix. If my_matcher is declared in the it block it works as expected.
That looks like a bug. Can you file an issue with rspec-core?
I want to write some tree data structure in ruby. The class file:
class Tree
attr_accessor :node, :left, :right
def initialize(node, left=nil, right=nil)
self.node=node
self.left=left
self.right=right
end
end
The rspec file:
require 'init.rb'
describe Tree do
it "should be created" do
t2=Tree.new(2)
t1=Tree.new(1)
t=Tree.new(3,t1,t2)
t.should_not be nil
t.left.node should eql 1
t.right.node should eql 2
end
end
Rspec keeps complaining:
1) Tree should be created
Failure/Error: t.left.node should eql 1
ArgumentError:
wrong number of arguments (0 for 1)
# ./app/tree.rb:3:in `initialize'
# ./spec/tree_spec.rb:9:in `block (2 levels) in <top (required)>'
Why?? I move the spec code into the class file and it works out. What is wrong?
Believe it or not, the problem is two missing dots in your rspec. These lines:
t.left.node should eql 1
t.right.node should eql 2
should be this:
t.left.node.should eql 1
t.right.node.should eql 2
Insert that period before should, and your spec should pass.
Here's what's going on. The should method works on any value, but if you call it bare, like this:
should == "hello"
it will operate on the subject of your test. What's the subject? Well, you can set the subject to whatever you want using the subject method, but if you don't, rspec will assume the subject is an instance of whatever class is being described. It sees this at the top of your spec:
describe Tree
and tries to create a subject like this:
Tree.new
which blows up, since your initialize won't work without any arguments; it needs at least one. The result is a pretty cryptic error if you didn't intend to write a should with an implicit subject.