Using BDD with web-services: a tutorial using JBehave and Thucydides
Join the DZone community and get the full member experience.
Join For Freebehavior driven development (bdd) is an approach that uses conversions around concrete examples to discover, describe and formalize the behavior of a system. bdd tools such as jbehave and cucumber are often used for writing web-based automated acceptance testing. but bdd is also an excellent approach to adopt if you need to design a web service. in this article, we will see how you can use jbehave and thucydides to express and to automate clear, meaningful acceptance criteria for a restful web service. (the general approach would also work for a web service using soap.) we will also see how the reports (or "living documentation", in bdd terms) generated by these automated acceptance criteria also do a great job to document the web service.
thucydides is an open source bdd reporting and acceptance testing library that is used in conjunction with other testing tools, either bdd testing frameworks such as jbehave , cucumber or specflow , or more traditional libraries such as junit.
web services are easy to model and test using bdd techniques, in many ways more so than web applications. web services are (or should be) relatively easy to describe in behavioral terms. they accept a well-defined set of input parameters, and return a well-defined result. so they fit well into the typical bdd-style way of describing behavior, using given-when-then format:
given some precondition when something happens then a particular outcome is expected
during the rest of this article we will see how to describe and automate web service behavior in this way. to follow along, you will need java and maven installed on your machine (i used java 8 and maven 3.2.1). the source code is also available on github (https://github.com/thucydides-webtests/webservice-demo). if you want to build the project from scratch, first create a new thucydides/jbehave project from the command line like this:
mvn archetype:generate -dfilter=thucydides-jbehave
enter whatever artifact and group names you like: it doesn't make any difference for this example:
choose archetype: 1: local -> net.thucydides:thucydides-jbehave-archetype (thucydides automated acceptance testing project using selenium 2, junit and jbehave) choose a number or apply filter (format: [groupid:]artifactid, case sensitive contains): : 1 define value for property 'groupid': : com.wakaleo.training.webservices define value for property 'artifactid': : bdddemo define value for property 'version': 1.0-snapshot: : 1.0.0-snapshot define value for property 'package': com.wakaleo.training.webservices: : confirm properties configuration: groupid: com.wakaleo.training.webservices artifactid: bdddemo version: 1.0.0-snapshot package: com.wakaleo.training.webservices y: :
this will create a simple project set up with jbehave and thucydides. it is designed to test web applications, but it is easy enough to adapt to work with a restful web service. we don't need the demo code, so you can safely delete all of the java classes (except for the acceptancetestsuite class) and the jbehave .story files.
now, update the pom.xml
file to use the latest version of thucydides, e.g.
<properties> <project.build.sourceencoding>utf-8</project.build.sourceencoding> <thucydides.version>0.9.239</thucydides.version> <thucydides.jbehave.version>0.9.235</thucydides.jbehave.version> </properties>
once you have done this, you need to define some stories and scenarios for your web service. to keep things simple in this example, we will be working with two simple requirements: shortening and expanding urls using google's url shortening service . we will describe these in two jbehave story files. create a stories
directory under src/test/resources
, and create a sub-directory for each requirement called expanding_urls
and shortening_urls
. each directory represents a high-level capability that we want to implement. inside these directories we place jbehave story files ( expanding_urls.story
and shortening_urls.story
) for the features we need. (this structure is a little overkill in this case, but is useful for real-world project where the requirements are more numerous and more complex). this structure is shown here:
[img_assist|nid=167149|title=|desc=|link=popup|align=left|width=600|height=234]
the story files contain the bdd-style given-when-then scenarios that describe how the web service should behave. when you design a web service using bdd, you can express behavior at two levels (and many projects use both). the first approach is to describe the json data in the bdd scenarios, as illustrated here:
scenario: shorten urls given a url http://www.google.com when i request the shortened form of this url then i should obtain the following json message: { "kind": "urlshortener#url", "id": "http://goo.gl/fbss", "longurl": "http://www.google.com/" }
this works well if your scenarios have a very technical audience (i.e. if you are writing a web service purely for other developers), and if the json contents remain simple. it is also a good way to agree on the json format that the web sevice will produce. but if you need to discuss the scenario with business, bas or even testers, and/or if the json that you are returning is more complicated, putting json in the scenarios is not such a good idea. this approach also works poorly for soap-based web services where the xml message structure is more complex. a better approach in these situations is to describe the inputs and expected outcomes in business terms, and then to translate these into the appropriate json format within the step definition:
scenario: shorten urls given a url <providedurl> when i request the shortened form of this url then the shortened form should be <expectedurl> examples: | providedurl | expectedurl | | http://www.google.com/ | http://goo.gl/fbss | | http://www.amazon.com/ | http://goo.gl/xj57 |
let's see how we would automate this scenario using jbehave and thucydides. first, we need to write jbehave step definitions in java for each of the given/when/then steps in the scenarios we just saw. create a class called processingurls
next to the acceptancetestsuite
class, or in a subdirectory underneath this class.
urlshortenersteps
to do the heavy-weight work. this approach make a cleaner separation of th what from the how , and makes reuse easier - for example, if we need to change underlying web service we used to implement the url shortening feature, these step definitions should remain unchanged:
public class processingurls { string providedurl; string returnedmessage; @steps urlshortenersteps urlshortener; @given("a url <providedurl>") public void givenaurl(string providedurl) { this.providedurl = providedurl; } @when("i request the shortened form of this url") public void shortenurl() { returnedmessage = urlshortener.shorten(providedurl); } @when("i request the expanded form of this url") public void expandurl() { returnedmessage = urlshortener.expand(providedurl); } @then("the shortened form should be <expectedurl>") public void shortenedformshouldbe(string expectedurl) throws jsonexception { urlshortener.response_should_contain_shortened_url(returnedmessage, expectedurl); } }
now add the urlshortenersteps
class. this class contains the actual test code that interacts with your web service we could use any java rest client for this, but here we are using the spring resttemplate . the full class looks like this:
public class urlshortenersteps extends scenariosteps { resttemplate resttemplate; public urlshortenersteps() { resttemplate = new resttemplate(); } @step("longurl={0}") public string shorten(string providedurl) { map<string, string> urlform = new hashmap<string, string>(); urlform.put("longurl", providedurl); return resttemplate.postforobject("https://www.googleapis.com/urlshortener/v1/url", urlform, string.class); } @step("shorturl={0}") public string expand(string providedurl) { return resttemplate.getforobject("https://www.googleapis.com/urlshortener/v1/url?shorturl={shorturl}", string.class, providedurl); } @step public void response_should_contain_shortened_url(string returnedmessage, string expectedurl) throws jsonexception { string expectedjsonmessage = "{'id':'" + expectedurl + "'}"; jsonassert.assertequals(expectedjsonmessage, returnedmessage, jsoncomparemode.lenient); } @step public void response_should_contain_long_url(string returnedmessage, string expectedurl) throws jsonexception { string expectedjsonmessage = "{'longurl':'" + expectedurl + "'}"; jsonassert.assertequals(expectedjsonmessage, returnedmessage, jsoncomparemode.lenient); } }
the spring resttemplate class is an easy way to interact with a web service with a minimum of fuss. in the shorten()
method, we invoke the urlshortener
web service using a post operation to shorten a url:
@step("longurl={0}") public string shorten(string providedurl) { map<string, string> urlform = new hashmap<string, string>(); urlform.put("longurl", providedurl); return resttemplate.postforobject("https://www.googleapis.com/urlshortener/v1/url", urlform, string.class); }
the expand service is even simpler to call, as it just uses a simple get operation:
@step("shorturl={0}") public string expand(string providedurl) { return resttemplate.getforobject("https://www.googleapis.com/urlshortener/v1/url?shorturl={shorturl}", string.class, providedurl); }
in both cases, we return the json document produced by the web service, and verify the contents in the then step using the jsonassert library. there are many libraries you can use to verify the json data returned from a web service. if you need to check the entire json structure, jsonassert provides a convenient api to do so. jsonassert lets you match json documents strictly (all the elements must match, in the right order), or leniently (you only specify a subset of the fields that need to appear in the json document, regardless of order).
the following step checks that the json documet contains an id field with the expected url value. the full json document will appear in the reports because it is being passed as a parameter to this step.
@step public void response_should_contain_shortened_url(string returnedmessage, string expectedurl) throws jsonexception { string expectedjsonmessage = "{'id':'" + expectedurl + "'}"; jsonassert.assertequals(expectedjsonmessage, returnedmessage, jsoncomparemode.lenient); }
you can run these scenarios using mvn verify
from the command line: this will produce the test reports and the thucydides living documentation for these scenarios. once you have run mvn verify
, open the index.html
file in the target/site/thucydides
directory. this gives an overview of the test results. if you click on the requirements tab, you will see an overview of the results in terms of capabilities and features. we call this "feature coverage":
drill down into the "shorten urls" test result. here you will see a summary of the story or feature illustrated by this scenario:
and if you scroll down further, you will see the details of how this web service was tested, including the json document returned by the service:
bdd is a great fit for developing and testing web services. if you want to learn more about bdd, be sure to check out the bdd , tdd and test automation workshops we are running in sydney and melbourne this may!
john ferguson smart is a well-regarded consultant, coach, and trainer in technical agile practices based in sydney, australia. a prominent international figure in the domain of behaviour driven development, automated testing and software life cycle development optimisation, john helps organisations around the world to improve their agile development practices and to optimise their java development processes and infrastructures. he is the author of several books, most recently bdd in action for manning.
Opinions expressed by DZone contributors are their own.
Comments