How to use `or` in RSpec equality matchers (Rails) - ruby-on-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

Related

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.)

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.

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

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.

How do you compare multiple RHS values in RSPec?

I'm new to RSpec, and I'm trying to run "should == A || B", but it's ignoring the 'B' and is only comparing with 'A' (and thus failing when val is 'B'):
Sample.find(:all).map(&:param).each{|val| val.should == 'A'||'B'}
Does anyone know how I include the 'B' in the comparison?
['A', 'B'].should include(val)
That might get your spec passing, but is it what you want to test? That the return value is a member of a set? If so, then perhaps this is a good solution.
You can also do:
(x == A || x == B).should be_true
Sample.find(:all).map(&:param).each{ |val| ['A', 'B'].should.include?(value) }
However, this does seem like a bit of an odd test to write.
Assuming you are trying to perform an existence check, I would rewrite your code as follows:
Sample.exists?(:conditions => {:params => %w(A B)}).should_be_true

RSpec: Match an array of strings by regex

I am testing validation of my models with rspec and am expecting an error message. However, the exact text of the message is likely to change, so I want to be a bit more forgiving and only check for a partial message.
Since the Spec::Matchers::include method only works for strings and collections, I'm currently using this construct:
#user.errors[:password].any?{|m|m.match(/is too short/)}.should be_true
This works but seems a bit cumbersome to me. Is there a better (i.e., faster or more ruby-like) way to check an array for the inclusion of a string by regex, or perhaps an rspec matcher that does just this?
I would recommend doing
#user.errors[:password].to_s.should =~ /is too short/
Simply because it will give you a more helpful error when it fails. If you use be_any then you get a message like this...
Failure/Error: #user.errors[:password].should be_any{ |m| m =~ /is too short/}
expected any? to return true, got false
However, if you use the to_s method then you will get something like this:
Failure/Error: #user.errors[:password].to_s.should =~ /is too short/
expected: /is to short/
got: "[]" (using =~)
Diff:
## -1,2 +1,2 ##
-/is too short/
+"[]"
So you can see the reason for the failure and don't have to go digging much to figure out why it is failing.
Using RSpec 3 expect syntax with matchers composing:
To match all:
expect(#user.errors[:password]).to all(match /some message/)
To match any:
expect(#user.errors[:password]).to include(match /some message/)
expect(#user.errors[:password]).to include a_string_matching /some message/
You can put the following code in spec/support/custom_matchers.rb
RSpec::Matchers.define :include_regex do |regex|
match do |actual|
actual.find { |str| str =~ regex }
end
end
Now you can use it like this:
#user.errors.should include_regex(/is_too_short/)
and be sure you have something like this in spec/spec_helper.rb
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
I don't think it makes a performance difference, but a more RSpec-like solution would be
#user.errors[:password].should be_any { |m| m =~ /is too short/ }
Both above answer are good. I would, however, use the newer Rspec expect syntax
#user.errors[:password].to_s.should =~ /is too short/
becomes
expect(#user.errors[:password].to_s).to match(/is too short/)
More great info here: http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
My solution to this is similar to #muirbot's. I use a custom matcher. However, I use the real include matcher, but augment it with the custom matcher as an argument. Load this somewhere before your suite runs (e.g. in spec/support/matchers.rb, in turn loaded by spec/spec_helper.rb):
RSpec::Matchers.define(:a_string_matching) do |expected|
match do |actual|
actual =~ expected
end
end
Then your expectation can be written like this:
expect(#user.errors[:password]).to include(a_string_matching(/is too short/))
Just another option
#user.errors[:password].grep /is too short/

Resources