Category Archives: Spring Web Flow

Measuring navigation flow JUnit test in Spring Web flow

Currently I am exploring Spring Web flow. Someone has already written a utility for measuring how well you are testing the Spring Web Flow web application navigation flow using JUnit. In this blog I am extending this utility to work with Sonar. The working project is here and Sonar plugin is here @ Github.

For people in hurry,

  • Get the latest code
  • Build the Spring Web Flow Test Sonar plugin using “mvn package”
  • Deploy sonar plugin jar located in the target folder into Sonar extension location and Start sonar
  • Run “mvn test sonar:sonar” on Spring Web Flow Coverage project
  • Finally you can go to Sonar and see the report as below

Problem Statement: Measuring Spring Web flow test coverage

In any web application there will be a page navigation flow. Frameworks like Spring Web flow, can externalize the page navigation as well as session management between each page in the navigation. In the below example, there is a simple usecase, where in we are presented with a food menu. Based on the menu presented, we can drill down further to findout more or we can cancel if we are not interested.

Details

Based on the usecase there are overall 13 distinct navigation paths in the Spring Web Flow. We can simulate all these paths by first writing the JUnit test and use the library to keep track of all the paths tested. In our test case only 7 paths are covered, out of 13. The sonar report is as below,

Spring WebFlow flow coverage

Utility to measure the navigation flow covered in a web application

We will adopt the Test first approach, by building the JUnit test to test the flow as per this blog.  The psedocode is as below,

private XMLFlowCoveragePathListenerAdapter listener = new XMLFlowCoveragePathListenerAdapter("test-flow");

public void testStartFlow() {
XMLFlowCoveragePathListenerAdapter.setTestName("testStartFlow");
XMLFlowCoveragePathListenerAdapter.iniliatizePath();

//test and assert the flow
}

public void testNextItemInFlow() {
XMLFlowCoveragePathListenerAdapter.setTestName("testNextItemInFlow");
XMLFlowCoveragePathListenerAdapter.iniliatizePath();

//test and assert the flow
}

public void testSecondItemInFlow() {
XMLFlowCoveragePathListenerAdapter.setTestName("testSecondItemInFlow");
XMLFlowCoveragePathListenerAdapter.iniliatizePath();

//test and assert the flow

//FINALLY END THE COVERAGE
XMLFlowCoveragePathListenerAdapter.endCoverage();
}

Conclusion

This utility is in a pre-alpha state. Currently it only show information on how many navigation flows it has covered against the total number as a percentage. There are few enhancements we need to take care of. For example, we need to provide a drilldown on what are the navigation flow that is not covered. We also need to build a threshold, where if it is less than that, it should break the maven build. Ofcourse, we need to cleanup the code.

I feel even in its present form this utility is useful. Feel free to use it.

JUnit testing with Spring Webflow

Spring Webflow sample

I love SpringSource related technologies because of the supporting classes they provide for Test Driven Development (TDD). I do TDD for 2 reasons,

  • I design the objects using spring webflow application from nothing, writing the tests first and evolving the code efficiently to support the tests and making the test success and finally the building the application
  • In the process, I am building a regression testing framework, a “safety net”. The advantage of this is when we change the backend, I always make sure the tests I have build will surely succeed and in the process the design matures

All the spring frameworks Spring WebFlow being no exception do a good job of this. In this blog I will show you how I do TDD for a simple Spring WebFlow. Those who are new to Spring WebFlow, it is a framework that support data/session management between pages, when you navigate in a workflow within a user session. For example, in the below example, I will talk about the hotel booking application where there is a submit booking, proceed/confirm or cancel booking(2 step webflow). Spring WebFlow helps in managing the session between these 2 steps (pages) without you worrying about session/request data management.

Luckily someone has already built this simple hotel booking application. The way I would build this application is, I will start off with a test as below,

public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {

private BookingService bookingService;

protected void setUp() {
bookingService = EasyMock.createMock(BookingService.class);
}

@Override
protected FlowDefinitionResource getResource(
FlowDefinitionResourceFactory resourceFactory) {
return resourceFactory
.createFileResource("src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml");
}

@Override
protected void configureFlowBuilderContext(
MockFlowBuilderContext builderContext) {
builderContext.registerBean("bookingService", bookingService);
}

public void testStartBookingFlow() {
Booking booking = createTestBooking();

EasyMock.expect(bookingService.createBooking(1L, "keith")).andReturn(
booking);

EasyMock.replay(bookingService);

MutableAttributeMap input = new LocalAttributeMap();
input.put("hotelId", "1");
MockExternalContext context = new MockExternalContext();
context.setCurrentUser("keith");
startFlow(input, context);

assertCurrentStateEquals("enterBookingDetails");
assertResponseWrittenEquals("enterBookingDetails", context);
assertTrue(getRequiredFlowAttribute("booking") instanceof Booking);

EasyMock.verify(bookingService);
}

public void testEnterBookingDetails_Proceed() {
setCurrentState("enterBookingDetails");
getFlowScope().put("booking", createTestBooking());

MockExternalContext context = new MockExternalContext();
context.setEventId("proceed");
resumeFlow(context);

assertCurrentStateEquals("reviewBooking");
assertResponseWrittenEquals("reviewBooking", context);
}

public void testReviewBooking_Confirm() {
setCurrentState("reviewBooking");
getFlowScope().put("booking", createTestBooking());
MockExternalContext context = new MockExternalContext();
context.setEventId("confirm");
resumeFlow(context);
assertFlowExecutionEnded();
assertFlowExecutionOutcomeEquals("bookingConfirmed");
}

private Booking createTestBooking() {
Hotel hotel = new Hotel();
hotel.setId(1L);
hotel.setName("Jameson Inn");
User user = new User("keith", "pass", "Keith Donald");
Booking booking = new Booking(hotel, user);
return booking;
}
}

AbstractXmlFlowExecutionTests is an interesting class as a part of spring-webflow.jar which provides all the plumbing for testing the flow and asserting the next steps within a spring webflow. Every test method that tests the flow will start with a “testXXX” method name. To begin with, there are no flow configuration file “src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml”. The test will obviously will fail. As a first step, we need to define the spring flow as below,

<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" />
<binding property="checkoutDate" />
<binding property="beds" />
<binding property="smoking" />
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
<binding property="amenities" />
</binder>
<on-render>
<render fragments="body" />
</on-render>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>

<view-state id="reviewBooking" model="booking">
<on-render>
<render fragments="body" />
</on-render>
<transition on="confirm" to="bookingConfirmed">
<evaluate expression="bookingService.persistBooking(booking)" />
</transition>
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="cancel" />
</view-state>

<end-state id="bookingConfirmed"/>

<end-state id="cancel" />

Once you add this configuration in “src/main/webapp/WEB-INF/hotels/booking/booking-flow.xml” file, and run the test again, the test will succeed.

If you notice carefully, transition on=”confirm” will call the business logic on the service layer, in this example, it will call “bookingService.persistBooking(booking)” business logic.

You also see that there are different controller methods like, enterBookingDetails and reviewBooking. These are associated with different view jsp file. But right now they are mocked and we just assert the controllers as in setCurrentState(“enterBookingDetailsreviewBooking”); and assertCurrentStateEquals(“enterBookingDetailsreviewBooking”); in JUnit test.

Now if we have to build views, we will have 2 views jsps, and configure them in view.xml as below,

<definition name="enterBookingDetails" extends="standardLayout">
<put-attribute name="body" value="/WEB-INF/hotels/booking/enterBookingDetails.jsp"/>
</definition>

<definition name="reviewBooking" extends="standardLayout">
<put-attribute name="body" value="/WEB-INF/hotels/booking/reviewBooking.jsp" />
</definition>

You can see the jsp pages in the location as mentioned above, but in the JSP file the key thing that contribute to the webflow is adding,

<form:form modelAttribute="booking" action="${flowExecutionUrl}">

The flowExecutionUrl spring webflow expression language variable will control the flow to next page. There are several such variable within Spring Webflow, for more details click here.

If you want to run this example, you just need to do

mvn tomcat:run

Other References: