RSpec matcher that checks collection to include item that satisfies lambda
In RSpec 3, matchers are fully composable, which means you can pass any object which implements Ruby's ===
protocol (including a matcher!) to include
and it will work properly. Lambdas in Ruby 1.9 implement the ===
protocol, which means you can do this:
expect(changes).to include(lambda { |x| x.key == 'name' && value == 'test' })
That said, that's not going to give you a great failure message (since RSpec has no way to generate a description of the lambda). I'm not sure where value
comes from in your example, but if it is intended to be x.value
, you could use the have_attributes
(or an_object_having_attributes
for better readability) matcher:
expect(changes).to include an_object_having_attributes(key: 'name', value: 'test')
Expect assertion that is only true for one element in an array
RSpec has composing matchers that can be used with expect(array).to include
to achieve what I wanted. For example:
expect(array).to include(a_string_matching("expected value"))
For the have_attributes
matcher specifically, RSpec has an alias for it called an_object_having_attributes
, allowing me to write:
expect(array).to include(an_object_matching(object_i_want_duplicated))
Negating a custom RSpec matcher that contains expectations
I had to look for it, but I think it's nicely described here in the rspec documentation
Format (from the docs) for separate logic when using expect.not_to
:
RSpec::Matchers.define :contain do |*expected|
match do |actual|
expected.all? { |e| actual.include?(e) }
end
match_when_negated do |actual|
expected.none? { |e| actual.include?(e) }
end
end
RSpec.describe [1, 2, 3] do
it { is_expected.to contain(1, 2) }
it { is_expected.not_to contain(4, 5, 6) }
# deliberate failures
it { is_expected.to contain(1, 4) }
it { is_expected.not_to contain(1, 4) }
end
Better way to assert that all user.name in an array of user start with a prefix using rspec?
In a binary test like this, I would create two users, one that starts with an s, the other without. I would then check that only the expected element was returned.
like
set up a user(:name => "Sam") and user(:name => "Fred")
filtered_users.map(&:name).should =~ ["Sam"]
In the case of failure, you will see something like
expected ["Sam"]
got ["Fred", "Sam"]
This is much more explicit about what you are doing
When to use curly braces vs parenthesis in expect Rspec method?
In response to OP's comment, I've edited and completely rewritten my answer. I realize that my original answer was oversimplified, so much so that it could be considered incorrect.
Your question was actually addressed somewhat by this other StackOverflow question.
One poster, Peter Alfvin, makes a good point when he says:
As for rules, you pass a block or a Proc if you're trying to test
behavior (e.g. raising errors, changing some value). Otherwise, you
pass a "conventional" argument, in which case the value of that
argument is what is tested.
The reason you're encountering the phenomenon you're seeing has to do with the raising of errors. When you pass @parser.parse('adsadasdas')
as an argument (use parentheses) to expect
, you are essentially telling ruby:
- Evaluate
@parser.parse('adsadasdas')
first. - Take the result and pass this to
expect
. expect
should see if this result matches my expectation (that is, thatErrno:ENOENT
will be raised).
But, what happens is: when ruby evaluates @parser.parse('adsadasdas')
, an error is raised right then and there. Ruby doesn't even get a chance to pass the result on to expect
. (For all we care, you could have passed @parser.parse('adsadasdas')
as an argument to any function... like multiply()
or capitalize()
) The error is raised, and expect
never even gets a chance to do its work.
But when you pass @parser.parse('adsadasdas')
as a proc (a code block) to expect
using curly braces, what you are telling ruby is this:
expect
, get ready to do some work.expect
, I would like you to keep track of what happens as we evaluate@parser.parse('adsadasdas')
.- Ok,
expect
, did the code block that was just evaluated raise aErrno:ENOENT
error? I was expecting that it would.
When you pass a code block to expect
, you are telling expect
that you want it to examine the resulting behavior, the changes, made by your code block's execution, and then to let you know if it meets up to the expectations that you provide it.
When you pass an argument to expect
, you are telling ruby to evaluate that argument to come to some value before expect
even gets involved, and then you are passing that value to expect
to see if it meets up to some expectation.
Related Topics
Rmagick How to Convert Image in Memory
How to Get a List from a Ruby Enumerable
Rails: Uninitialized Constant Just Happen on Production Server
Custom To_Yaml and Domain_Type
System New Line Separator in Ruby
How Can Same Program in Ruby Accept Input from User as Well as Command Line Arguments
Bundler::Rubyversionmismatch: Your Ruby Version Is 1.9.3, But Your Gemfile Specified 2.0.0
Ruby on Rails - Paperclip Not Saving to Database
How to Combination/Permutation in Ruby
How to Reinstall Ruby with Readline Support
Jekyll Plugin Not Work on Github
Error: Error Installing JSON: Error: Failed to Build Gem Native Extension
Restart Rails Server Automatically After Every Change in Controllers
Which Plugins/Gems Should I Use to Dynamically Generate Thumbnails On-The-Fly in Rails 3
Adding Fields to Devise Sign Up Using Rails 4