Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asserting that a particular exception is thrown in Cucumber

Scenario

I'm writing a library (no Ruby on Rails) for which I'd like to have very detailed Cucumber features. This especially includes describing errors/exceptions that should be thrown in various cases.

Example

The most intuitive way to write the Cucumber steps would probably be something like

When I do something unwanted
Then an "ArgumentError" should be thrown

Problem

There are two issues I have to address:

  1. The first step should not fail when an exception is thrown.
  2. The exception that the first step throws should be accessible to the second step in order to do some assertion magic.

Unelegant And Cumbersome Solution

The best approach I've been able to come up with is caching the exception in the first step and putting it into an instance variable that the second step can access, like so:

When /^I do something unwanted$/ do
  begin
    throw_an_exception!
  rescue => @error
  end
end

Then /^an "(.*)" should be thrown$/ do |error|
  @error.class.to_s.should == error
end

However, this makes the first step more or less useless in cases where I don't want it to fail, and it requires an instance variable, which is never a good thing.

So, can anyone help me out with an at least less cumbersome solution? Or should I write my features differently anyway? Any help would be much appreciated.

like image 218
JLimperg Avatar asked Feb 09 '12 22:02

JLimperg


People also ask

Can we use assert in Cucumber?

Cucumber does not come with an assertion library. Instead, use the assertion methods from a unit testing tool.

How do I assert runtime exception in JUnit?

When using JUnit 4, we can simply use the expected attribute of the @Test annotation to declare that we expect an exception to be thrown anywhere in the annotated test method. In this example, we've declared that we're expecting our test code to result in a NullPointerException.


2 Answers

I thought about it once more, and maybe the answer is:

There is no elegant solution, because the Given-When-Then-Scheme is violated in your case. You expect that "Then an exception should be thrown" is the outcome of "When I do something unwanted".

But when you think about it, this is not true! The exception is not the outcome of this action, in fact the exception just shows that the "When"-Statement failed.

My solution to this would be to test at a higher level:

When I do something unwanted
Then an error should be logged

or

When I do something unwanted
Then the user should get an error message

or

When I do something unwanted
Then the program should be locked in state "error"

or a combination of these.

Then you would "cache the exception" in your program - which makes perfect sense, as you most likely need to do that anyway.

The two problems you've stated would be solved, too.

In case you really must test for exceptions

Well, i guess then cucumber isn't the right test suite, hmm? ;-)

As the Given-When-Then-Scheme is violated anyway, I would simply write

When I do something unwanted it should fail with "ArgumentError"

and in the step definitions something like (untested, please correct me if you try it)

When /^I do something unwanted it should fail with "(.*)"$/ do |errorstring|
  expect {
    throw_an_exception!
  }.to raise_error(errorstring)
end

As said above, that is horribly wrong as the scheme is broken, but it would serve the purpose, wouldn't it? ;-)

You'll find further documentation at testing errors at rspec expectations.

like image 139
user562529 Avatar answered Oct 09 '22 17:10

user562529


One option is to mark the scenario with @allow-rescue and check the page's output and status code. For example

In my_steps.rb

Then(/^the page (?:should have|has) content (.+)$/) do |content|
  expect(page).to have_content(content)
end

Then(/^the page should have status code (\d+)$/) do |status_code|
  expect(page.status_code.to_s).to eq(status_code)
end

Then /^I should see an error$/ do
  expect(400..599).to include(page.status_code)
end

In my_feature.feature

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then the page should have content Routing Error
  And the page should have status code 404

or alternatively:

@allow-rescue
Scenario: Make sure user can't do XYZ
  Given some prerequisite
  When I do something unwanted
  Then I should see an error

This may not be exactly what you were hoping for, but it might be an acceptable workaround for some people who come across this page. I think it will depend on the type of exception, since if the exception is not rescued at any level then the scenario will still fail. I have used this approach mostly for routing errors so far, which has worked fine.

like image 23
Nate Cook Avatar answered Oct 09 '22 17:10

Nate Cook