This is a revisited version of an old example
This is a new implementation of the same example using yatspec but in a more clear (in my oppinion) way. The old post is located here.
Why clearer? We are not using unnecessary objects like StateExtractor, ActionUnderTest and hamcrest matchers which we do not like.
What is the story?
The story we will be implementing is “Buy the stocks when price drops by 10 units”. When we receive a notification through a web service that we expose, we will have to examine it and buy stocks when certain conditions are met.
What is Yatspec?
Yatspec is a new test framework that can meet your needs if Cucumber, Concordion and Fitnesse don’t. This example will focus on getting the story done using Yatspec. I will prepare other posts on using other acceptance test frameworks soon. There will be also another post that compares the frameworks. I will link to then as soon as I implement them.
Step 1: Start with an acceptance test skeleton
I came up with an acceptance test skeleton. This is how it looks like:
@RunWith(SpecRunner.class)
public class BuyStocksAcceptanceTest extends TestState {
@Test
public void buyStocksWhenPriceDrops() throws Exception {
whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived();
thenABiddingRequestFor1000googleStocksWasSentToStockMarket();
}
private void thenABiddingRequestFor1000googleStocksWasSentToStockMarket() {
throw new UnsupportedOperationException("not yet implemented");
}
private void whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived() {
throw new UnsupportedOperationException("not yet implemented");
}
}
When I run it I see of course:
java.lang.UnsupportedOperationException: not yet implemented
at com.testdrivendevelopment.example.acceptance.BuyStocksAcceptanceTest.whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived(BuyStocksAcceptanceTest.java:21)
at com.testdrivendevelopment.example.acceptance.BuyStocksAcceptanceTest.buyStocksWhenPriceDrops(BuyStocksAcceptanceTest.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
(...)
Html output:
/tmp/com/testdrivendevelopment/example/acceptance/BuyStocksAcceptanceTest.html
When I open the output HTML file I see:
Notice that the HTML file is generated from Java code. This makes it very easy to refactor all Java stuff and keep documentation always up to date, since the documentation is generated. You don’t have to edit any HTML files!
Step 2: Time to implement the acceptance tests
I implement the aceptance tests. I have never used Jersey before, but I decided to do it this time since it is said to be the leading solution when it comes to RESTful communication in Java. Please forgive me if my use of Jersey is not optimal. Please remember, in this article we focus on introducing Yatspec.
Please notice that we have an Application object that represents our application. We also have a StockMarketStubServer which is a stub of the real stock market that we will be sending requests to.
As you can see below the acceptance test sends a notification to the application and the verifies that the stock market received the expected bid request.
I also add information to interestingGivens and log(…). Those elements will be visible in the HTML output.
@RunWith(SpecRunner.class)
public class BuyStocksAcceptanceTest extends TestState {
private final Application application = new Application();
private final StockMarketStubServer stockMarketStubServer = new StockMarketStubServer();
private final String googleStockName = "Google";
@Before
public void setUp() throws Exception {
application.start();
stockMarketStubServer.start();
}
@After
public void tearDown() throws Exception {
application.stop();
stockMarketStubServer.stop();
}
@Test
public void buyStocksWhenPriceDrops() throws Exception {
whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived();
thenABiddingRequestFor1000googleStocksWasSentToStockMarket();
}
private void thenABiddingRequestFor1000googleStocksWasSentToStockMarket() {
String requestSentToStockMarket = stockMarketStubServer.popOnlyRequest();
log("Request received by stock market system", requestSentToStockMarket);
int expectedNumUnits = 1000;
interestingGivens.add("Expected stocks to buy", expectedNumUnits);
Message message = stockMarketStubServer.popOnlyMessage();
assertThat(message.getRequestType()).isEqualTo(BID);
assertThat(message.getStockName()).isEqualTo(googleStockName);
assertThat(message.getUnits()).isEqualTo(expectedNumUnits);
}
private void whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived() {
String priceDrop = "-10";
WebResource webResource = postNotificationToApplication(priceDrop);
interestingGivens.add("Price drop", priceDrop);
interestingGivens.add("Stock name", googleStockName);
log("Received notification", webResource);
}
private WebResource postNotificationToApplication(String priceDrop) {
DefaultClientConfig clientConfig = new DefaultClientConfig();
Client client = Client.create(clientConfig);
WebResource resource = client.resource(application.getBaseUri());
WebResource webResource = resource.path("/notification")
.queryParam("stockName", googleStockName)
.queryParam("units", priceDrop);
webResource.post();
return webResource;
}
}
Step 3: Make the acceptance test pass
I implement the production code. I don’t include the listings in here since the purpose of this article is to focus on Yatspec. If you wish to see the end result please take a look at the attached sources.
Yatspec final HTML output
I run the test. Now we can take a look at the final Yatspec documentation/output, HTML!
You can click the headers “Interesting Givens”, “Received Notification” and “Request Received By Stock Market System” to expands those sections.
Notice that we can log the conversations with other systems. One can use the CapturedInputAndOutputs object, that is the log(…) method that will be automaticly added to the HTML output. Notice that we can highligt interesting stuff in the conversations without editing HTML, using IntegestingGives object! When you are doing ATDD in backend systems this “logging” might be very useful. I would say that in most cases the “captured inputs and outputs” are actually the acceptance criteria.
Conclusions
This post was just a simple quick introduction to Yatspec. I am working on comparing Yatspec with other test frameworks. I will update the posts as soon as I am ready. Please comment and subscribe to RSS! :)