ChatGPT解决这个技术问题 Extra ChatGPT

RSpec: Expect to change multiple

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.


M
Maksim Kalmykov

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.


if you want to expect that multiple things are not changed, simply use .and change { @something }.by(0)
Can you add a second example with all the parenthesis ? I's hard to understand which methods are chained
My answer works for Ruby 2 and seems to work with .should_not for anyone who needs it
M
Matthew Hinea

If you want to test that multiple records were not changed, you can invert a matcher using RSpec::Matchers.define_negated_matcher. So, add

RSpec::Matchers.define_negated_matcher :not_change, :change

to the top of your file (or to your rails_helper.rb) and then you can chain using and:

expect{described_class.reorder}.to not_change{ruleset.reload.position}.
    and not_change{simple_ruleset.reload.position}

C
CupawnTae

BroiSatse's answer is the best, but if you are using RSpec 2 (or have more complex matchers like .should_not), this method also works:

lambda {
  lambda {
    lambda {
      lambda {
        click_button 'Save'
        @user.reload
      }.should change {@user.name}.from('donald').to('gustav')
    }.should change {@user.updated_at}.by(4)
  }.should change {@user.great_field}.by_at_least(23)
}.should change {@user.encrypted_password}

Ah, nice idea! You could probably build a wrapper to make it a bit easier to read!
F
Foo Bar Zoo

The accepted answer is not 100% correct since the full compound matcher support for change {} has been added in RSpec version 3.1.0. If you try to run the code given in accepted answer with the RSpec version 3.0, you would get an error.

In order to use compound matchers with change {}, there are two ways;

First one is, you have to have at least RSpec version 3.1.0.

Second one is, you have to add def supports_block_expectations?; true; end into the RSpec::Matchers::BuiltIn::Compound class, either by monkey patching it or directly editing the local copy of the gem. An important note: this way is not completely equivalent to the first one, the expect {} block runs multiple times in this way!

The pull request which added the full support of compound matchers functionality can be found here.


关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now