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:

About these ads

3 thoughts on “JUnit testing with Spring Webflow

  1. Pingback: JUnit testing of Spring MVC application: Testing Spring Webflow | Krishna's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s