Rspec-Rails: Testing a method with a lot of combinations of arguments - ruby-on-rails

I have a method that I want to test for different paramters if it does the right thing.
What I am doing right now is
def test_method_with(arg1, arg2, match)
it "method should #{match.inspect} when arg2 = '#{arg2}'" do
method(arg1, FIXEDARG, arg2).should == match
end
end
context "method with no info in arg1" do
before (:each) do
#ex_string = "no info"
end
test_method_with(#ex_string, "foo").should == "res1"}
test_method_with(#ex_string, "bar").should == "res1"}
test_method_with(#ex_string, "foobar").should == "res1"}
test_method_with(#ex_string, "foobar2").should == "res2"}
test_method_with(#ex_string, "barbar").should == "res2"}
test_method_with(#ex_string, nil).should == nil}
end
But this is really not so DRY to repeat the method over and over again... What would be a better way to accomplish this? More in the way the "table" option of cucumber does it (it is just about the right behaviour of a helper method, so to use cucumber does not seem right).

Your method expects 3 arguments, but you're passing it two.
That being said, you can write a loop to call it multiple times, like this:
#don't know what arg2 really is, so I'm keeping that name
[ {arg2: 'foo', expected: 'res1'},
{arg2: 'bar', expected: 'res1'},
#remaining scenarios not shown here
].each do |example|
it "matches when passed some fixed arg and #{example[:arg2]}" do
method(#ex_string, SOME_CONSTANT_I_GUESS,example[:arg2]).should == example[:expected]
end
end
This way, you only have one example (aka the it call) and your examples are extracted to a data table (the array containing the hashes).

I think your approach is fine if you remove the passing of the instance variable #ex_string. (And the match occurring only in test_method_with as Kenrick suggests.) That said you could use a custom matcher:
RSpec::Matchers.define :match_with_method do |arg2, expected|
match do
method(subject, arg2) == expected
end
failure_message_for_should do
"call to method with #{arg2} does not match #{expected}"
end
end
it 'should match method' do
"no info".should match_with_method "foo", "res1"
end
matchers can be placed in the spec helper file for access from several specs.

Related

Write rspec for method inside method

def some_helper(exam)
x = 1
y = 2
if condition1
x = 3
y = 4
end
if condition2
x = 5
y = 6
end
return_something_base_on_x_y(x,y)
end
def return_something_base_on_x_y(x,y)
return "#{1}/#{2}" if x==1, y==2
return "#{3}/#{4}" if x==3, y==4
return "#{5}/#{6}" if x==5, y==6
end
i will call in view like this
some_helper(exam) # exam is an object
How can i write rspec for some_helper ? Can i write something like bellow. Only test the argument pass to method
describe "#some_helper" do
let(:exam) { Exam.create exam_params }
context "condition 1" do
it do
expect "some_helper" already call return_something_base_on_x_y with arguments(1,2) inside them
expect "some_helper" already call return_something_base_on_x_y with arguments(3,4) inside them
expect "some_helper" already call return_something_base_on_x_y with arguments(5,6) inside them
end
end
end
Can i avoid to write like
expect(some_helper(exam)).to eq "123" # and 456.
Because if condition is more complexity, i have to get a list of return_something_base_on_x_y result.
You can set expectations on a method before it is called by using a double:
it "sets an expectation that a method should be called"
obj = double('obj')
expect(obj).to recive(:foo).with('bar')
obj.foo('bar')
end
The example is failed if obj.bar is not called.
You can set expectations on an object after the call is done by using spies:
obj = spy('obj')
obj.foo('bar')
expect(obj).to have_recived(:foo).with('bar')
This allows you to arrange your tests after the act, arrange, assert pattern (or given, then, when in BDD terms).
Can i avoid to write like
expect(some_helper(exam)).to eq "123" # and 456.
Yes, but it might actually degrade your tests. Stubbing can mask bugs and makes your code more about testing the implementation (how the code does its job) then the behavior (the result).
Stubbing is most suitible when the object you're testing touches an application boundry or is not idempotent (like for example a method that generates random values).

method.to_proc doesn't return from enclosed function

I was trying to DRY up a Rails controller by extracting a method that includes a guard clause to return prematurely from the controller method in the event of an error. I thought this may be possible using a to_proc, like this pure Ruby snippet:
def foo(string)
processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
My thinking was that having called to_proc on the breaker method, calling the early return statement in the rescue clause should escape the execution of foo. However, it didn't work:
2.4.0 :033 > foo('bar')
This step should not be executed in the event of an error
=> "BAR"
2.4.0 :034 > foo(2)
Well you messed that up, didn't you?
This step should not be executed in the event of an error
=> nil
Can anyone please
Explain why this doesn't work
Suggest a way of achieving this effect?
Thanks in advance.
EDIT: as people are wondering why the hell I would want to do this, the context is that I'm trying to DRY up the create and update methods in a Rails controller. (I'm trying to be agressive about it as both methods are about 60 LoC. Yuck.) Both methods feature a block like this:
some_var = nil
if (some complicated condition)
# do some stuff
some_var = computed_value
elsif (some marginally less complicated condition)
#error_msg = 'This message is the same in both actions.'
render partial: "show_user_the_error" and return
# rest of controller actions ...
Hence, I wanted to extract this as a block, including the premature return from the controller action. I thought this might be achievable using a Proc, and when that didn't work I wanted to understand why (which I now do thanks to Marek Lipa).
What about
def foo(string)
processed = breaker(string)
puts "This step should not be executed in the event of an error"
processed
rescue ArgumentError
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
raise ArgumentError.new("could not call upcase! on #{string.inspect}")
end
string
end
After all this is arguably a pretty good use case for an exception.
It seems part of the confusion is that a Proc or lambda for that matter are distinctly different than a closure (block).
Even if you could convert Method#to_proc to a standard Proc e.g. Proc.new this would simply result in a LocalJumpError because the return would be invalid in this context.
You can use next to break out of a standard Proc but the result would be identical to the lambda that you have now.
The reason Method#to_proc returns a lambda is because a lambda is far more representative of a method call than a standard Proc
For Example:
def foo(string)
string
end
bar = ->(string) { string } #lambda
baz = Proc.new {|string| string }
foo
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
bar.()
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
baz.()
#=> nil
Since you are converting a method to a proc object I am not sure why you would also want the behavior to change as this could cause ambiguity and confusion. Please note that for this reason you can not go in the other direction either e.g. lambda(&baz) does not result in a lambda either as metioned Here.
Now that we have explained all of this and why it shouldn't really be done, it is time to remember that nothing is impossible in ruby so this would technically work:
def foo(string)
# place assignment in the guard clause
# because the empty return will result in `nil` a falsey value
return unless processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
Example

Testing if a method returns a `non-null` value

Im testing if my method populate() returns a non-null value (which it does, it returns an integer > 0) but having troubles to correctly write it. I have:
describe House::Room do
describe '.populate' do
let(:info) {
$info = {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to eq(nil)
end
end
end
You'll need to change the let assignment to:
describe House::Room do
describe '.populate' do
let(:info) {"people"=>
{"name"=>"Jordan",
"last_name"=>"McClalister"}
}
it 'should return an integer > 0' do
expect(House::Room.populate(info)).not_to be(nil)
end
end
end
That should make your code work as you expect.
However, you could also use another matcher, like 'be_within' if you wanted to be more specific, or write several expect statements in the same test, like 'expect to be an integer', 'expect to be greater than 0', etc... There is no limit to the number of expect statements you can have in an 'it' block, the test will only pass if all of the expectations are fulfilled. (That said, I believe best practice would be to split it up into individual tests.)

How to use `or` in RSpec equality matchers (Rails)

I'm trying to do something like
expect(body['classification']).to (be == "Apt") || (be == "House")
Background:
This is testing a JSON API response.
Issue:
I want the test to pass if either "Apt" or "House" are returned. But in the test it is only comparing to the first value, "Apt".
Failure/Error: expect(body['classification']).to be == "Apt" or be == "House"
expected: == "Apt"
got: "House"
Previous Solution:
There is a solution here,
(Equality using OR in RSpec 2) but its depreciated now, and I wasn't able to make it work.
Documentation:
Also wasn't able to find examples like this in the documentation (https://www.relishapp.com/rspec/rspec-expectations/v/3-4/docs/built-in-matchers/equality-matchers)
Is this possible to do?
How about this:
expect(body['classification'].in?(['Apt', 'Hourse']).to be_truthy
Or
expect(body['classification']).to eq('Apt').or eq('Hourse')
Or even this:
expect(body['classification']).to satify { |v| v.in?(['Apt', 'Hourse']) }
expect(body['classification']).to eq("Apt").or eq("House")
Based on this link
"Compound Expectations.
Matchers can be composed using and or or to make compound expectation
Use or to chain expectations"
RSpec.describe StopLight, "#color" do
let(:light) { StopLight.new }
it "is green, yellow or red" do
expect(light.color).to eq("green").or eq("yellow").or eq("red")
end

What is the best way to test if nested list is as expected?

When running a unit test, I'm expecting a method I am testing to return a nested array like this:
[
{:identifier=>"a", :label=>"a label",
:sublist=>[{:identifier=>"sublist z", :label=>"z sublist label"}, {:identifier=>" sublist w", :label=>"sublist w label"}]},
{:identifier=>"b", :label=>"b label",
:sublist=>[{:identifier=>"sublist y", :label=>"y sublist label"}]},
..]
What is the most elegant way to check if the array returned is what I expect it to be?
I'm using Minitest Spec if that makes any difference.
Btw, the order of elements does not matter and may vary.
Thx.
In this case, it would be ideal to write a custom matcher for minitest.
Here, is the code that you would need to add in the matcher.
def match_hash(h1, h2)
matched = false
h1.each do |ele|
h2.each do |ele2|
match_elements?(ele, ele2) ? (matched = true) : next
end
if !matched
return matched
end
end
matched
end
def match_elements?(ele, ele2)
if (ele[:identifier] != ele2[:identifier]) || (ele[:label] != ele2[:label])
return false
end
if ele.has_key?(:sublist) && ele2.has_key?(:sublist)
return match_hash(ele[:sublist], ele2[:sublist])
end
true
end
Write your custom matcher using this example
Then use match_hash in your test case to compare the two hashes.
NOTE: The above code has been tested in irb and it works perfectly.

Resources