Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 942
Thilo Avatar asked Nov 10 '10 15:11

Thilo


6 Answers

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.

like image 96
radixhound Avatar answered Nov 08 '22 11:11

radixhound


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/
like image 25
Maciej Majewski Avatar answered Nov 08 '22 13:11

Maciej Majewski


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}
like image 9
muirbot Avatar answered Nov 08 '22 13:11

muirbot


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/ }
like image 8
mikeweber Avatar answered Nov 08 '22 11:11

mikeweber


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/))
like image 3
Jimmy Avatar answered Nov 08 '22 11:11

Jimmy


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

like image 6
Neal Avatar answered Nov 08 '22 12:11

Neal