I want to check for many changes in a model when submitting a form in a feature spec. For example, I want to make sure that the user name was changed from X to Y, and that the encrypted password was changed by any value.
I know there are some questions about that already, but I didn't find a fitting answer for me. The most accurate answer seems like the ChangeMultiple
matcher by Michael Johnston here: Is it possible for RSpec to expect change in two tables?. Its downside is that one only check for explicit changes from known values to known values.
I created some pseudo code on how I think a better matcher could look like:
expect {
click_button 'Save'
}.to change_multiple { @user.reload }.with_expectations(
name: {from: 'donald', to: 'gustav'},
updated_at: {by: 4},
great_field: {by_at_leaset: 23},
encrypted_password: true, # Must change
created_at: false, # Must not change
some_other_field: nil # Doesn't matter, but want to denote here that this field exists
)
I have also created the basic skeleton of the ChangeMultiple
matcher like this:
module RSpec
module Matchers
def change_multiple(receiver=nil, message=nil, &block)
BuiltIn::ChangeMultiple.new(receiver, message, &block)
end
module BuiltIn
class ChangeMultiple < Change
def with_expectations(expectations)
# What to do here? How do I add the expectations passed as argument?
end
end
end
end
end
But now I'm already getting this error:
Failure/Error: expect {
You must pass an argument rather than a block to use the provided matcher (nil), or the matcher must implement `supports_block_expectations?`.
# ./spec/features/user/registration/edit_spec.rb:20:in `block (2 levels) in <top (required)>'
# /Users/josh/.rvm/gems/ruby-2.1.0@base/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:268:in `load'
# /Users/josh/.rvm/gems/ruby-2.1.0@base/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:268:in `block in load'
Any help in creating this custom matcher is highly appreciated.
In RSpec 3 you can setup multiple conditions at once (so the single expectation rule is not broken). It would look sth like:
expect {
click_button 'Save'
@user.reload
}.to change { @user.name }.from('donald').to('gustav')
.and change { @user.updated_at }.by(4)
.and change { @user.great_field }.by_at_least(23}
.and change { @user.encrypted_password }
It is not a complete solution though - as far as my research went there is no easy way to do and_not
yet. I am also unsure about your last check (if it doesn't matter, why test it?). Naturally you should be able to wrap it within your custom matcher.