In my previous post I showed you how to use Cucumber-JVM to execute a JUnit test. We’ll continue with that example in this post by refactoring the work we’ve done.
Open DepositStepDefinitons.java and notice the 2 private classes, User and Account which we added. Clearly, these are our domain objects that the real system will need, and that other scenarios would likely make use of too. So lets extract these private classes in to their own class files and make them public.
Once you create the User.java and Account.java classes, execute RunTests.java to make sure the scenario is still passing. Still passes? Great!
The next section I would like to highlight is the problem in our When step: When £100 is deposited in to the account. If this step was reused in another scenario we could potentially have a problem with the account object, it may be null, since we were initializing it in the Given step which we might not use in a new scenario. So lets refactor this part of the class so we always have a correctly initialized domain.
Like with regular JUnit steps there are hooks to allow us to run some code before and after each test, or in our case, each scenario. Cucumber has the same hooks, cucumber.annotation.Before. Refactor your class to look like this:
src/test/java/com/c0deattack/cucumberjvmtutorial/DepositStepDefinitions.java
package com.c0deattack.cucumberjvmtutorial;
import static org.junit.Assert.assertTrue;
import com.c0deattack.cucumberjvmtutorial.domain.Account;
import com.c0deattack.cucumberjvmtutorial.domain.User;
import cucumber.annotation.Before;
import cucumber.annotation.en.Given;
import cucumber.annotation.en.Then;
import cucumber.annotation.en.When;
public class DepositStepDefinitions {
private User user;
private Account account;
@Before
public void initialize() {
if (user == null) {
user = new User();
}
if (account == null) {
account = new Account();
user.setAccount(account);
}
}
@Given("^a User has no money in their account$")
public void a_User_has_no_money_in_their_current_account() {
assertTrue("The balance is not zero.", account.getBalance() == 0L);
}
@When("^£(\\d+) is deposited in to the account$")
public void £_is_deposited_in_to_the_account(int amount) {
account.deposit(amount);
}
@Then("^the balance should be £(\\d+)$")
public void the_balance_should_be_£(int expectedBalance) {
int currentBalance = account.getBalance();
assertTrue("The expected balance was £100, but actually was: " + currentBalance, currentBalance == expectedBalance);
}
}
Here, I’ve added an initialize method with a @Before annotation. This annotation will execute for every scenario, regardless of which Feature the scenario is in. What would make this better is if we can tell Cucumber to only invoke that Before hook on specific scenarios. We can actually do this, by tagging the Scenarios, and passing the same tag expression to the @Before annotation, like this:
src/test/java/com/c0deattack/cucumberjvmtutorial/DepositStepDefinitions.java
package com.c0deattack.cucumberjvmtutorial;
import static org.junit.Assert.assertTrue;
import com.c0deattack.cucumberjvmtutorial.domain.Account;
import com.c0deattack.cucumberjvmtutorial.domain.User;
import cucumber.annotation.Before;
import cucumber.annotation.en.Given;
import cucumber.annotation.en.Then;
import cucumber.annotation.en.When;
public class DepositStepDefinitions {
private User user;
private Account account;
@Before(value="@UserAccounts")
public void initialize() {
if (user == null) {
user = new User();
}
if (account == null) {
account = new Account();
user.setAccount(account);
}
}
// rest of class....
}
src/test/resources/com/c0deattack/cucumberjvmtutorial/deposit.feature
Feature: Depositing money in to a User account @UserAccounts Scenario: Depositing money in to User's account should add money to the User's current balance Given a User has no money in their account When £100 is deposited in to the account Then the balance should be £100
If you wanted a particular tag expression to apply to the all Scenarios within a Feature file then you can move the tag expression up to the first line of the file, before the word Feature.
This pretty much concludes the basics of refactoring your Cucumber step definitions. Some key points to remember:
- Make your steps reusable by thinking about the objects they interact with and the lifecycle of the objects
- Use the@Before/@After hooks to setup/teardown data
- Use tag expressions to specify when you want the @Before/@After hooks to be applied
Please leave a comment if you have any questions.
#1 by tilm4nn on August 24, 2012 - 7:46 pm
I am currently looking for BDD test tools for Java and this one looks nice.
Two questions:
1. Why the annotation for the initialization, why not automatically execute exactly the before blocks of classes that hold step definitions that are used by the scenario to be executed?
2. What would be the best approach to have common state used by steps defined in different step definition classes?
I am also evaluating Jnario http://jnario.org/ at the moment which also nicely integrates with java.
Keep up the good work, Tilmann
#2 by c0deattack on August 26, 2012 - 1:57 pm
Hi Tilmann,
In response;
1) There is nothing stopping you from having “Given user accounts exists” in your Scenarios, however you might think that the step is ‘noise’ in understanding what is important for the test. In my example above, I felt that is important to focus on the user not having any money in their account.
2) There are a few ways, I’ve seen some people put instance variables in Step Definition classes, this might be ok if the steps in the same Class are the only ones that will ever need access to that variable, and since it’s in instance variable and not static you won’t leak state between scenarios. However, my method is to create a ‘Context’ class which has a Map instance class member. Anything I need between steps gets stored in the Context. The context is emptied after each scenario completes.
Have not come across Jnario, will take a look though, thanks.
#3 by tilm4nn on August 27, 2012 - 4:23 pm
Thanks for the hint on #2
Sorry my #1 was missleading. I meant: Why is cucumber that way? Why does it execute every Before block it finds and not only the Before blocks of classes that hold steps used in the executing scenario. That would make much more sense to me. And it would make the special tagging of scenarios regarding which Before blocks are required unnecessary.
#4 by c0deattack on September 12, 2012 - 10:26 pm
I guess because sometimes it is useful to be able to run @Before for all scenarios. For instance if you were using Selenium WebDriver with Cucumber you could have a new Browser window open for each scenario, or have it clear cookies before each scenario etc. I think it would be hard to only execute @Before blocks for steps based on the Class they’re in, a Given step can also be used as a When step, in that case you might not need the @Before block, hope that makes sense.
#5 by seggy on September 24, 2012 - 6:29 am
Thanks for this example. I am very grateful indeed. You talked about having more examples in Draft, hope you are going to post them soon. Great stuff !!!
#6 by choesang on January 4, 2013 - 11:21 am
Thank you for making it so easy to start with cucumber. It allowed us to have a smooth start.
#7 by rhonda on May 13, 2013 - 2:26 pm
this is great. thank you for providing a complete example. you helped me easily get started.
#8 by MiB on May 20, 2013 - 10:23 pm
When you do this example with cucumber-jvm 1.1.3 you run into this issue: https://github.com/cucumber/cucumber-jvm/issues/491. The solution is to change the “pretty” to the ‘progress’ formatter or another in the @Cucumber.Options of RunTests.java.