JBehave is a java framework for Behaviour-Driven Development (BDD). JBehave has Selenium support for browser based functional testing but it can also be used to test interfaces
without a user interface. This post has an example of using JBehave and CXF to test a webservice, a public webservice to convert numbers to words will be used:
http://www.dataaccess.com/webservicesserver/numberconversion.wso?WSDL
This wsdl has an operation NumberToWords which simply converts a number to words e.g. 123 -> one hundred and twenty three – soapUI can easily invoke this webservice if you want to see it in action first.
CXF is an Open-Source Services Framework, it offers a whole lot more but we will just be using the cxf-codegen-plugin to generate a JAX-WS client to consume the webservice.
JBehave has supports Spring / Guice and PicoContainer Dependency Injection (DI) support, in this example we will use Guice.
JBehave can be tricky to configure when starting out but fortunately it comes has some handy maven archetypes to kick start a JBehave project, so lets use one to create our project, first run the command:
mvn archetype:generate -Dfilter=org.jbehave:jbehave
When prompted, enter the following:
Choose archetype:
1: remote -> org.jbehave:jbehave-groovy-archetype (An archetype to run multiple textual stories with steps classes written in Groovy.)
2: remote -> org.jbehave:jbehave-guice-archetype (An archetype to run multiple textual stories configured programmatically but with steps classes
composed using Guice.)
3: remote -> org.jbehave:jbehave-pico-archetype (An archetype to run multiple textual stories configured programmatically but with steps classes
composed using Pico.)
<snip>
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 2
Choose org.jbehave:jbehave-guice-archetype version:
<snip>
14: 3.5.3
15: 3.5.4
16: 3.6-beta-1
17: 3.6-beta-2
18: 3.6-beta-3
Choose a number: 18: 15
Define value for property 'groupId': : me.samlewis.blog
Define value for property 'artifactId': : jbehave-cxf
Define value for property 'version': 1.0-SNAPSHOT: 1.0
Define value for property 'package': me.samlewis.blog: me.samlewis.blog.jbehavecxf
[INFO] Using property: jbehaveCoreVersion = 3.5.4
[INFO] Using property: jbehaveSiteVersion = 3.1.1
Confirm properties configuration:
groupId: me.samlewis.blog
artifactId: jbehave-cxf
version: 1.0
package: me.samlewis.blog.jbehavecxf
jbehaveCoreVersion: 3.5.4
jbehaveSiteVersion: 3.1.1
Y: Y
<snip>
[INFO] BUILD SUCCESSFUL
Now we can go ahead and build the project:
cd jbehave-cxf
mvn verify site
Hopefully you will see “BUILD SUCCESSFUL”.
Some irrelevant spring configuration is included in the this guice application so lets delete src\main\resources\me\samlewis\blog\jbehavecxf\my_steps.xml.
Next, lets create our story. First, rename src\main\resources\me\samlewis\blog\jbehavecxf\stories\my.story to number_to_words.story and change the contents to:
Scenario: one digit number is converted to words
Given a number of 4
When the number is converted to a word
Then the word is four
Scenario: two digit number is converted to words
Given a number of 23
When the number is converted to words
Then the words are twenty three
Scenario: three digit number is converted to words
Given a number of 945
When the number is converted to words
Then the words are nine hundred and forty five
Scenario: four digit number is converted to words
Given a number of 8361
When the number is converted to words
Then the words are eight thousand three hundred and sixty one
So we have four scenarios in our story. JBehave searches for Given / When / Then ‘Step’ annotations to match the text in our story when it runs so having not implemented any steps we expect the build to fail when this command is run:
mvn clean verify
The build fails but conveniently JBehave tells us the steps we need to implement:
Scenario: one digit number is converted to words
Given a number of 4 (PENDING)
When the number is converted to a word (PENDING)
Then the word is four (PENDING)
@Given("a number of 4")
@Pending
public void givenANumberOf4(){
// PENDING
}
@When("the number is converted to a word")
@Pending
public void whenTheNumberIsConvertedToAWord(){
// PENDING
}
@Then("the word is four")
@Pending
public void thenTheWordIsFour(){
// PENDING
}
So lets go ahead to implement all of the steps, edit src\main\java\me\samlewis\blog\jbehavecxf\steps\MySteps.java changing the contents to:
package me.samlewis.blog.jbehavecxf.steps;
import static junit.framework.Assert.assertEquals;
import java.math.BigInteger;
import org.jbehave.core.annotations.Alias;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
public class MySteps {
private BigInteger number;
private String result;
@Given("a number of $number")
public void givenANumber(BigInteger number){
this.number = number;
}
@When("the number is converted to a word")
@Alias("the number is converted to words")
public void whenTheNumberIsConvertedToWords(){
throw new IllegalStateException("TODO: implement me");
}
@Then("the word is $words")
@Alias("the words are $words")
public void thenTheWordIs(String words){
assertEquals(words, result);
}
}
The @Alias annotation can be used to make the stories more readable, in this example we use have singular and plural aliases. Variables are bound to method parameters for example in the thenTheWordIs() method the $words variable is bound to the words method parameter.
The whenTheNumberIsConvertedToWords() method currently just throws an IllegalStateException because we aren’t ready to call the web service just yet. So lets use cxf to generate classes to invoke our webservice, add this plugin config to our pom.xml file:
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>2.3.1</version>
<executions>
<execution>
<phase>generate-sources</phase>
<configuration>
<wsdlOptions>
<wsdlOption>
<wsdl>http://www.dataaccess.com/webservicesserver/numberconversion.wso?WSDL</wsdl>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
Now, build again:
mvn clean verify
And the sourcecode to call this web service should be generated in target/generated-sources/cxf.
So now we can go ahead and implement the unimplemented method in our steps:
import java.net.MalformedURLException;
import java.net.URL;
import org.jbehave.core.annotations.Alias;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
import com.dataaccess.webservicesserver.NumberConversion;
import com.dataaccess.webservicesserver.NumberConversionSoapType;
import com.google.inject.Inject;
import com.google.inject.name.Named;
public class MySteps {
private BigInteger number;
private String result;
private NumberConversionSoapType numberConversionSoapType;
@Inject
public MySteps(@Named("number.conversion.wsdl") String wsdlLocation){
try{
NumberConversion numberConversion = new NumberConversion(new URL(wsdlLocation));
numberConversionSoapType = numberConversion.getNumberConversionSoap();
}
catch (MalformedURLException e){
throw new RuntimeException("Invalid wsdlLocation", e);
}
}
@Given("a number of $number")
public void givenANumber(BigInteger number){
this.number = number;
}
@When("the number is converted to a word")
@Alias("the number is converted to words")
public void whenTheNumberIsConvertedToWords(){
result = numberConversionSoapType.numberToWords(number);
//remove trainling space
result = result.trim();
}
@Then("the word is $words")
@Alias("the words are $words")
public void thenTheWordIs(String words){
assertEquals(words, result);
}
}
The constructor has Guice annotations to inject a property for the wsdl url and the whenTheNumberIsConvertedToWords() method invokes the numberToWords operation. There is an extra line to trim the result because the public web service result has a trailing space.
So, one last step is to configure guice, first create the file src/main/resources/config.properties with the contents:
number.conversion.wsdl=http://www.dataaccess.com/webservicesserver/numberconversion.wso?WSDL
Then edit the file /src/main/java/me/samlewis/blog/jbehavecxf/MyStories.java modifying the guice module inner class:
public static class StepsModule extends AbstractModule {
@Override
protected void configure() {
try{
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("/config.properties"));
Names.bindProperties(binder(), properties);
bind(MySteps.class).in(Scopes.SINGLETON);
}
catch (IOException e){
throw new RuntimeException("Failed to load peoperties", e);
}
}
}
Guice is setup to load our properties file so the wsdl url can be injected. So now we can build again build again, one last time:
mvn clean verify
The build should succeed and a nice report written to target/jbehave/view/me.samlewis.blog.jbehavecxf.stories.number_to_words.html. You are probably going to want to run tests from your IDE during development, you can run MyStories as a JUnit test.
This post has barely scratched the surface of JBehave but it demonstrates that you can use JBehave to test programmatic interfaces such as Web Services / EJBs / Restful Services etc etc.
The sourcecode for this example can be downloaded here.