Reputation: 8470
I have a method which performs some actions on Cat model and in case of incorrect input raises an exception:
context "hungry cat" do
it { expect { eat(what: nil) }.to raise_error }
end
What I want to do is to check whether this method change cats status, like that:
context "hungry cat" do
it { expect { eat(what: nil) }.to raise_error }
it { expect { eat(what: nil) }.not_to change(cat, :status) }
end
The problem is that since eat(what: nil)
will raise an exception the second it
will fail no matter what. So, is it possible to ignore exception and check some condition?
I know that it's possible to do something like:
it do
expect do
begin
eat(what: nil)
rescue
end
end.not_to change(cat, :status)
end
But it's way too ugly.
Upvotes: 46
Views: 26221
Reputation: 4930
You can chain positive assertions with and
. If you want to mix in a negated one in the chain, RSpec 3.1 introduced define_negated_matcher
.
You could do something like:
RSpec::Matchers.define_negated_matcher :not_change, :change
expect { eat(what: nil) }
.to raise_error
.and not_change(cat, :status)
Inspired by this comment.
Upvotes: 48
Reputation: 3006
You can also put expectations inside expectation blocks. It's still a little ugly, but it should work:
expect do
# including the error class is just good practice
expect { cat.eat(what: nil) }.to raise_error(ErrorClass)
end.not_to change { cat.status }
Upvotes: 1
Reputation: 2048
In RSpec 3 you can chain the two tests into one. I find this to be more readable than the rescue nil
approach.
it { expect { eat(what: nil) }.to raise_error.and not_to change(cat, :status)}
Upvotes: 9
Reputation: 13633
You could use the "rescue nil" idiom to shorten what you already have:
it { expect { eat(what: nil) rescue nil }.not_to change(cat, :status) }
Upvotes: 41
Reputation: 16793
It sounds strange that the eat(what: nil)
code isn't run in isolation for each of your tests and is affecting the others. I'm not sure, but perhaps re-writing the tests slightly will either solve the issue or more accurately identify where the problem is (pick your flavour below).
Using should
:
context "hungry cat" do
context "when not given anything to eat" do
subject { -> { eat(what: nil) } }
it { should raise_error }
it { should_not change(cat, :status) }
end
end
Using expect
:
context "hungry cat" do
context "when not given anything to eat" do
let(:eating_nothing) { -> { eat(what: nil) } }
it "raises an error" do
expect(eating_nothing).to raise_error
end
it "doesn't change cat's status" do
expect(eating_nothing).to_not change(cat, :status)
end
end
end
Upvotes: 0