abpetkov
abpetkov

Reputation: 914

Expect multiple not_to change expectations in rspec

I'm trying to make sure certain data has remain unchanged by a single action:

expect {
  # running migration and user reload here
}.not_to change(user, :avatar_url).from(sample_avatar_url).and change(user, :old_avatar).from(nil)

sample_avatar_url is a string defined in the beginning of the spec file.

Basically, I want to check whether the avatar_url and old_avatar remain untouched by what's happening in the expect block.

The output from the above code is:

expect(...).not_to matcher.and matcher is not supported, since it creates a bit of an ambiguity. Instead, define negated versions of whatever matchers you wish to negate with RSpec::Matchers.define_negated_matcher and use expect(...).to matcher.and matcher.

Upvotes: 25

Views: 14588

Answers (3)

h2no
h2no

Reputation: 11

i know it is long ago but i had a similar problem and did something like this:

expect {
  # running migration and user reload here
}.not_to change { user.slice(:avatar_url, :old_avatar) }

Upvotes: 1

cbliard
cbliard

Reputation: 7272

As stated in Thomas answer, it does not work because it's not clear reading and you can create a negated matcher. Another option is to use the saharspec gem and its dont matcher negation.

Here is an extract from the project's README:

Another (experimental) attempt to get rid of define_negated_matcher. dont is not 100% grammatically correct, yet short and readable enought. It just negates attached matcher.

# before
RSpec.define_negated_matcher :not_change, :change

it { expect { code }.to do_stuff.and not_change(obj, :attr) }

# after: no `define_negated_matcher` needed
require 'saharspec/matchers/dont'

it { expect { code }.to do_stuff.and dont.change(obj, :attr) }

So you can write your expectation without creating a negated matcher like this:

expect {
  # running migration and user reload here
}.to dont.change(user, :avatar_url).from(sample_avatar_url).and 
    dont.change(user, :old_avatar).from(nil)

Upvotes: 0

Thomas Walpole
Thomas Walpole

Reputation: 49950

This doesn't work because it's not clear reading whether thats supposed to mean not change the first and not change the second, or not change the first but change the second. You have a couple of options to get around this

Since you're checking static values just don't use change

..run migration and user reload..
expect(user.avatar_url).to eq(sample_avatar_url)
expect(user.old_avatar).to eq nil

or use define_negated_matcher to create a not_change matcher

RSpec::Matchers.define_negated_matcher :not_change, :change
expect {
  # running migration and user reload here
}.to not_change(user, :avatar_url).from(sample_avatar_url).and not_change(user, :old_avatar).from(nil)

Upvotes: 44

Related Questions