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/ }
                        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/))
                        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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With