RSpec: Match an array of strings by regex - ruby-on-rails

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/

Related

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

Match multiple yields in any order

I want to test an iterator using rspec. It seems to me that the only possible yield matcher is yield_successive_args (according to https://www.relishapp.com/rspec/rspec-expectations/v/3-0/docs/built-in-matchers/yield-matchers). The other matchers are used only for single yielding.
But yield_successive_args fails if the yielding is in other order than specified.
Is there any method or nice workaround for testing iterator that yields in any order?
Something like the following:
expect { |b| array.each(&b) }.to yield_multiple_args_in_any_order(1, 2, 3)
Here is the matcher I came up for this problem, it's fairly simple, and should work with a good degree of efficiency.
require 'set'
RSpec::Matchers.define :yield_in_any_order do |*values|
expected_yields = Set[*values]
actual_yields = Set[]
match do |blk|
blk[->(x){ actual_yields << x }] # ***
expected_yields == actual_yields # ***
end
failure_message do |actual|
"expected to receive #{surface_descriptions_in expected_yields} "\
"but #{surface_descriptions_in actual_yields} were yielded."
end
failure_message_when_negated do |actual|
"expected not to have all of "\
"#{surface_descriptions_in expected_yields} yielded."
end
def supports_block_expectations?
true
end
end
I've highlighted the lines containing most of the important logic with # ***. It's a pretty straightforward implementation.
Usage
Just put it in a file, under spec/support/matchers/, and make sure you require it from the specs that need it. Most of the time, people just add a line like this:
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
to their spec_helper.rb but if you have a lot of support files, and they aren't all needed everywhere, this can get a bit much, so you may want to only include it where it is used.
Then, in the specs themselves, the usage is like that of any other yielding matcher:
class Iterator
def custom_iterator
(1..10).to_a.shuffle.each { |x| yield x }
end
end
describe "Matcher" do
it "works" do
iter = Iterator.new
expect { |b| iter.custom_iterator(&b) }.to yield_in_any_order(*(1..10))
end
end
This can be solved in plain Ruby using a set intersection of arrays:
array1 = [3, 2, 4]
array2 = [4, 3, 2]
expect(array1).to eq (array1 & array2)
# for an enumerator:
enumerator = array1.each
expect(enumerator.to_a).to eq (enumerator.to_a & array2)
The intersection (&) will return items that are present in both collections, keeping the order of the first argument.

Rspec expect(method) vs expect(method).to be_true

I've just started diving into TDD with RSpec and Rails and I've found that if I'm testing a method that should come out to be true that the test will work if i use expect(method) instead of expect(method).to be_true.
it "is true" do
expect(subject).to be_true
#Works the same as...
expect(subject)
end
Is there any reason to not use the expect without .to, will it break the tests or not catch certain failures? Just wondering.
You are not achieving what you think you are. expect(false) does not fail. expect(true) only succeeds because nothing is being tested.
For example if you had a spec with the following:
describe "something" do
it "does something" do
false
puts expect(false)
# anything that evaluates to false
end
end
You would get something like this when you run rspec:
#<RSpec::Expectations::ExpectationTarget:0x007ff0b35a28b8\>
.
Finished in 0.00061 seconds
1 example, 0 failures
Essentially you are not asserting anything and expect(subject) evaluates to ExpectationTarget.
I think it is a coincidence that the latter works.
Every expect statement evaluates to true or false one way or the other.
For example
arr = ['a', 'b', 'c']
expect(arr).to include('a')
will return true because arr includes 'a'.
So in your case expect(subject) and expect(subject).to be_true is somewhat equivalent to the following:
bool = true
if bool
# Do something here
if bool == true
# Do something here
To make a long story short, it doesn't matter, but I would prefer expect(subject).to be_true for readability purposes.

How does RSpec's expect... to change... to... work internally?

In RSpec, there is matcher expect{}.to change{}.to like
expect{employee.change_name}.to change{employee.name}.to "Mike"
It is very easy to read, but is not that easy to understand how it works from language standpoint. I suppose that expect, to and change are methods, but what objects are they called at? What curly braces mean in that case?
Thank you.
change and expect are methods of self and to is a method of the result of executing change and expect. The {} expressions are blocks passed to change and expect.
The following illustrates the order of evaluation:
def self.to1(arg)
puts "to1(#{arg})"
"to1"
end
def self.to2(arg)
puts "to2(#{arg})"
"to2"
end
def self.expect
puts "expect"
yield
self
end
def self.change
puts "change"
yield
self
end
expect{puts "b1"}.to1 change{puts "b2"}.to2 "#{puts 'Mike' ; 'Mike'}"
which produces the following output:
expect
b1
change
b2
Mike
to2(Mike)
to1(to2)
=> "to1"
They are blocks in ruby.
Basically the first step towards lambda expressions, basically anonymous functions.

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.

Resources