RSpec stop method - ruby

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)

Related

Calling private methods by symbol name in Ruby

I have the symbol name of a method that I'd like to call with some arguments. What I'm really trying to do boils down to this code snippet:
method.to_proc.call(method)
In this case, method is the symbol name of a method on the object. In my case, I'm trying to call a method that happens to be private on the object.
This is the error output that I get:
>$ ruby symbol_methods.rb
symbol_methods.rb:33:in `call': private method `test_value_1' called for "value":String (NoMethodError)
from symbol_methods.rb:33:in `block (2 levels) in <main>'
from symbol_methods.rb:30:in `each'
from symbol_methods.rb:30:in `block in <main>'
from symbol_methods.rb:29:in `each'
from symbol_methods.rb:29:in `<main>'
Here's a self-contained example that demonstrates this behavior:
data = [
["value", true],
["any value here", true],
["Value", true],
]
def matches_value(string)
string == "value"
end
def contains_value(string)
string.gsub(/.*?value.*?/, "\\1")
end
def matches_value_ignore_case(string)
string.downcase == "value"
end
#tests
[:matches_value, :contains_value, :matches_value_ignore_case].each_with_index do |method, index|
test = data[index]
value = test[0]
expected_result = test[1]
result = method.to_proc.call(value) # <<== HERE
puts "#{method}: #{result == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
end
The important bit is in the block marked #tests. The data variable is a set of inputs and expected results. The test_value_* methods are private methods that are the tests to run.
I've tried public_send(method, value) and method.to_proc.call(value), but both result in the private method error.
What would be the right way to call a private method named as a symbol in this case? I'm looking for both an explanation and a syntactically correct answer.
use send instead.
puts "#{method}: #{send(method, value) == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
After a fair amount of searching, I found an alternative answer than Object#send, that has an unanticipated feature benefit. The solution is to use the Object#method to return a Method object for the symbol name.
A Method object is a Proc-like callable object, so it implements the #call interface, which fits the bill nicely. Object has many such useful helpers defined in its interface.
In context of the original question, this is how it works:
#tests
[:test_value_1, :test_value_2, :test_value_3].each do |method|
data.each do |test|
value = test[0]
expected_result = test[1]
puts "#{method}: #{self.method(method).call(value) == expected_result ? 'Pass' : 'Fail'}: '#{value}'"
end
end
The important bits are:
self.method(method).call(value)
This will convert the symbol name to a Method object, and then invoke the method with value supplied as the parameter. This works roughly equivalently to the send method solution, in functional terms. However, there are some differences to note.
send is going to be somewhat more efficient, as there's no overhead in the conversion to a Method. Method#call and send use different internal calling mechanisms, and it appears that send has less call overhead, as well.
The unanticipated feature of using Object#method is that the Method object is easily converted to a Proc object (using Method#to_proc). As such, it can be stored and passed as a first-class object. This means that it can be supplied in place of a block or provided as a callback, making it useful for implementing flexible dispatch solutions.

rspec stub to allow [hash_key] to be passed

How do you create a rspec method stub to allow a response from a method that takes in the hash key to return its value?
This is the line I want to test
sub_total = menu.menu_items[item] * quantity
and I'm using this line in rspec as my test stub on a double.
allow(menu).to receive(:menu_items[item]).and_return(2.0)
My env is set up with ruby 2.2.0 and spec 3.1.7
However I keep on getting a
NameError: undefined local variable or method `item'
Ruby code
def place_order(item, quantity, menu)
sub_total = menu.menu_items[item] * quantity
#customer_order << [item, quantity, sub_total]
end
Rspec code
let(:menu) { double :menu }
it "should allow 1 order of beer to placed" do
order = Order.new
allow(menu).to receive(:menu_items[item]).and_return(2.0)
order.place_order(:Beer, 1, 2.0)
expect(order.customer_order).to eq [[:Beer, 1, 2.0]]
end
Failures:
1) Order should allow 1 order of beer to placed
Failure/Error: allow(menu).to receive(:menu_items[item]).and_return(2.0)
NameError:
undefined local variable or method `item' for #<RSpec::ExampleGroups::Order:0x007fbb62917ee8 #__memoized=nil>
# ./spec/order_spec.rb:9:in `block (2 levels) in <top (required)>'
I've tried a number of things but nothing has worked
allow(menu).to receive(:menu_items).and_return(2.0)
allow(menu).to receive(:menu_items).with(item).and_return(2.0)
allow(menu).to receive(:menu_items).with("item").and_return(2.0)
allow(menu).to receive(:menu_items).with([item]).and_return(2.0)
I've run my code in irb and I can see it works but I can't find a way to get my class double to recerive the hash key.
you can do this:
allow(menu.menu_items).to receive(:[]).and_return({Beer: 2.0})
You can also pass an specific item if you need:
allow(menu.menu_items).to receive(:[]).with(1).and_return({Beer: 2.0})
The line menu.menu_items[item] is in reality composed by 3 method calls. [] is a call to the method [] on the Hash returned by menu_items.
I assume menu.menu_items returns a Hash and not an Array, given in the spec item is a Symbol.
That means your stub requires a little bit more work.
allow(menu).to receive(:menu_items).and_return({ Beer: 2.0 })
Also note, the error
undefined local variable or method `item'
is because you were using item in the spec, but item is not defined outside your method.
you're going a little too deep with your stub, think of this instead
allow(menu).to receive(:menu_items).and_return({Beer: 2.0})
Thanks to #SimoneCarletti's answer, I was able to easily stub an instance of PublicActivity. I add this answer only as a more brief (re)statement of the OP's problem and the simplicity of the solution.
Code I want to mimic with a stub:
self.entity = activity.parameters['entity_string']
And the salient parts of the test double:
activity = double('activity') # PublicActivity
allow(activity).to receive(:parameters).and_return({'entity_string' => "some entity name"})
Full code:
class ActivityRenderer
attr_accessor :time
attr_accessor :user
attr_accessor :action
attr_accessor :entity
def initialize(activity)
self.entity = activity.parameters['entity_string']
self.time = activity.updated_at
self.user = User.find(activity.owner_id)
self.action = activity.key
end
end
RSpec.describe ActivityRenderer do
let(:user) { ...factory girl stuff... }
let(:now) { Time.zone.now }
before do
Timecop.freeze
end
it 'provides an activity renderer' do
activity = double('activity') # PublicActivity
allow(activity).to receive(:parameters).and_return({'entity_string' => "some entity name"})
allow(activity).to receive(:updated_at).and_return(now)
allow(activity).to receive(:owner_id).and_return(user._id)
allow(activity).to receive(:key).and_return('some activity?')
ar = ActivityRenderer.new(activity)
expect(ar.user).to eql(user)
expect(ar.time).to eql(now)
expect(ar.action).to eql('some activity?')
expect(ar.entity).to eql("some entity name")
end
end

In RSpec, using let to assign a regex creates unexpected pass/fail behavior - Bug or user error?

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?

payroll_items_controller_spec.rb:18:in `block (2 levels) displayed in Rspec Controller code

Below is the controller code in rspec for a master item.
To be very frank I'm very new to Ruby with a little knowledge of coding.
require 'spec_helper'
describe PayrollItemsController , "with valid params" do
before(:each) do
#payroll_item = mock_model(PayrollItem, :update_attributes => true)
PayrollItem.stub!(:find).with("1").and_return(#payroll_item)
end
it "should find PayrollItem and return object" do
PayrollItem.should_receive(:find).with("0").and_return(#payroll_item)
end
it "should update the PayrollItem object's attributes" do
#payroll_item.should_receive(:update_attributes).and_return(true)
end
end
When I run the controller code, following error displayed:
(Mock "PayrollItem_1001").update_attributes(any args)
expected: 1 time
received: 0 times
./payroll_items_controller_spec.rb:18:in `block (2 levels) in '
You have to actually make a request (get, post, put etc.) to the controller in order for the mock to have anything to check.
So for example:
it "should find PayrollItem and return object" do
PayrollItem.should_receive(:find).with("0").and_return(#payroll_item)
put :update, :id => "0"
end
In addition to that, looking at your code, you have some inconsistencies with your return values: in your before block you're stubbing PayrollItem.find with an id of 1 to return something, and then in your first spec you're mocking it with an id of 0 to return the same thing.
It's fine to both stub and mock the same method because they fulfill different functions: a stub makes sure that the code runs smoothly, while the mock actually checks an expectation. However, you should be stubbing/mocking it for the same argument, so that all the specs using this before block are testing the same thing.

Ruby. Mocking in RSpec

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

Resources