I finished the previous post with the promise that I’m going write more about hooks in cucumber. By definition, hooks allow us to perform actions at various points in the cucumber test cycle. The definition wisely doesn’t say much about these actions and the points where they are executed. They wary from performing prerequisite actions for scenarios to sending emails when a certain step fails. One can ask, What these have to do with the following scenario - src/test/resources/web_text_munger.feature
:
Feature: web text munger kata
Scenario: It should process a sentence
Given the embedded tomcat is running # NEW
And the application is deployed # NEW
And I am using Firefox for testing # NEW
And I am on the home page
When I enter "flow flow"
And I press "submit"
Then I see "folw folw" as the munged text
And I see "flow flow" as the original
And I close the browser # NEW (VERY BAD, DO NOT DO THIS HERE!)
The steps marked with # NEW
have nothing to do with the business case of the scenario. They are prerequisites that I need done if I want to run the scenario. For these, I need a different kind of approach: I’ll call these steps in different hooks.
Scenario Hooks
Scenario hooks can be defined with the cucumber.annotation.Before
and cucumber.annotation.After
annotations (JUnit has the exact same annotations in the org.junit
package, make sure you use the right ones as cucumber will not process JUnit annotations. Don’t try to use JUnit’s @BeforeClass
and @AfterClass
annotations either for the same reason – see also section about global hooks):
import cucumber.annotation.After;
import cucumber.annotation.Before;
@Before
public void beforeScenario() {
tomcat.start();
tomcat.deploy("munger");
}
@After
public void afterScenario() {
tomcat.stop();
}
Let’s remove the unnecessary lines from the web_text_munger.feature
and move their content to the scenario hooks:
Feature: web text munger kata
Scenario: It should process a sentence
Given I am on the home page
When I enter the "flow flow"
And I press "submit"
Then I see "folw folw" as munged text
And I see "flow flow" as the original
The WebTextMungerStepsdef.java
:
@Before
public void beforeScenario() {
tomcat.start();
tomcat.deploy("munger");
browser = new FirefoxDriver();
}
@After
public void afterScenario() {
browser.close();
tomcat.stop();
}
The scenario executes without any problems and it seems that we are done. Now let’s run the old simple_text_munger.feature
as well. No problem there either, but a Firefox window just popped up unexpectedly. It seems that the before hook has been executed twice. The methods annotated with @Before
and @After
are executed before and after every scenario regardless of where they are defined. That’s not good for us right now.
Tagged Hooks
It is possible to define a hook so that it is only executed before or after scenarios that are tagged with a specified annotation, for example:
Feature: web text munger kata
@web
Scenario: It should process a sentence
// The steps
The WebTextMungerStepsdef.java
:
@Before("@web")
public void beforeScenario() {
// actions
}
@After("@web")
public void afterScenario() {
// actions
}
It executes nicely and the test case is green. Let’s add another scenario to the web_text_munger.feature
. Both are green, no problems here, but the Firefox window popped up twice again. Since both of the scenarios have been tagged, the hooks have been executed twice: once for each scenario. If you have a lot of scenarios, it’s worth executing the before and after hooks only once in order to save time and resources.
Global Hooks: The Hacking
Unfortunately, cucumber doesn’t support global hooks at the moment. It is possible to solve this situation, but it is an ugly hack. Let’s use the first @web
annotation to start the web service and the browser - WebTextMungerStepsdef.java
:
private static EmbeddedTomcat tomcat = new EmbeddedTomcat();
private static WebDriver browser;
@Before("@web")
public void beforeScenario() {
if (!tomcat.isRunning()) {
tomcat.start();
tomcat.deploy("munger");
browser = new FirefoxDriver();
}
}
Now the tomcat
and browser
references have nice global states, that’s why I don’t really like this solution, but I don’t have a better one at the moment, so I’ll try to live with it.
Closing the browser and stopping the web service is a bit trickier, we don’t know which the last scenario that requires them is, so we should shut them down when the test suite exits. This solution is pretty much the same as in ruby because there the at_exit
is proposed to be used in cases like this - WebTextMungerStepsdef.java
:
@Before("@web")
public void beforeScenario() {
if (!tomcat.isRunning()) {
tomcat.start();
tomcat.deploy("munger");
browser = new FirefoxDriver();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
browser.close();
tomcat.stop();
}
});
}
}
Final Words
Don’t let the previous example discourage you from using hooks. They provide a great opportunity for setting up scenarios and cleaning up afterwards. The code is available under the episode_6 branch on github. Happy testing!
comments powered by Disqus