Andrew
Andrew

Reputation: 8470

How do I `expect` something which raises exception in RSpec?

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

Answers (5)

Jan Klimo
Jan Klimo

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

PJSCopeland
PJSCopeland

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

Sean
Sean

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

awendt
awendt

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

Paul Fioravanti
Paul Fioravanti

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

Related Questions