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