Monthly Archives: September 2012

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:

Advertisements

JUnit testing with Spring Integration and Spring WS

Spring Integration, Spring WS for Webservice is a decent framework to design and implement Webservice. More than that it has a good JUnit testing support. In this section I will be talking about how we do implement a Webservice using Test Driven Development (TDD). The working sample is here in the Github .

As a first step we need to define the XSD schema:

<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:data="http://mycompany.com/it/enterprise/data/v1" xmlns:msg="http://mycompany.com/it/enterprise/msg/v1" xmlns:svc="http://mycompany.com/it/enterprise/contract/TestService/v1" targetNamespace="http://mycompany.com/it/enterprise/contract/TestService/v1" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0">  <xsd:import namespace="http : //mycompany.com/it/enterprise/data/v1" schemaLocation="TestTransactions_v1.xsd"/>
<xsd:element name="TestServiceRequest" type="data:TestServiceRequestType"/>
<xsd:element name="TestServiceResponse" type="data:TestServiceResponseType"/>
</xsd:schema>

<?xml version="1.0" encoding="UTF-8"?><xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:data="http://mycompany.com/it/enterprise/data/v1" xmlns:msg="http://mycompany.com/it/enterprise/msg/v1" targetNamespace="http://mycompany.com/it/enterprise/data/v1" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:complexType name="TestServiceRequestType">
<xsd:sequence>
<xsd:element name="Document">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Id" type="xsd:string" minOccurs="0"/>
<xsd:element name="Type" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="TestServiceResponseType">
<xsd:sequence>
<xsd:element name="Document">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="Result" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

Writing the JUnit test as below,

public class TestIntegrationEndPointTest {

@Autowired
private ApplicationContext applicationContext;

private MockWebServiceClient mockClient;

@Before
public void createClient() {
mockClient = MockWebServiceClient.createClient(applicationContext);
}

@Test
public void testWsEndPointTest() throws Exception {
Source requestPayload = new StringSource("<?xml version=\"1.0\" encoding=\"UTF-8\"?><v1:TestServiceRequest xmlns:v1=\"http://mycompany.com/it/enterprise/contract/TestService/v1\" xmlns:v11=\"http://mycompany.com/it/enterprise/data/v1\"><v11:Document><v11:Id>101</v11:Id><v11:Type>MaterialMaster</v11:Type></v11:Document></v1:TestServiceRequest>");
Source responsePayload = new StringSource("<testServiceResponseType xmlns=\"http://mycompany.com/it/enterprise/data/v1\" xmlns:ns2=\"http://mycompany.com/it/enterprise/contract/TestService/v1\"><Document><Result>SUCCESS</Result></Document></testServiceResponseType>");

mockClient.sendRequest(withPayload(requestPayload)).andExpect(payload(responsePayload));
}
}

Now the test will still fail with need for Spring autowiring, the spring bean definition configuration for Spring WS looks as below,

<bean>
<property name="endpointMap">
<map>
<entry key="{http://mycompany.com/it/enterprise/contract/TestService/v1}TestServiceRequest" value-ref="TestBatchEndpoint" />
</map>
</property>
</bean>
<int-ws:inbound-gateway id="TestBatchEndpoint" reply-channel="test.batch.webservice.out" request-channel="test.batch.webservice.in" />
<bean id="marshaller" >
<property name="contextPath" value="com.mycompany.it.enterprise.contract.testservice.v1" />
</bean>
<bean id="testServiceActivator" class="com.mycompany.it.eis.webservice.ws.TestServiceActivator"/>
<bean id="resultTransformer" class="org.springframework.integration.xml.transformer.ResultToStringTransformer"/>

Spring Integration and Spring WS flow looks as below

<int:bridge  input-channel="test.batch.webservice.in" output-channel="test.batch.webservice.unmarshalling.in"></int:bridge>
<int-xml:unmarshalling-transformer id="defaultUnmarshaller" input-channel="test.batch.webservice.unmarshalling.in" output-channel="test.batch.webservice.unmarshalling.out" unmarshaller="marshaller"/>
<int:service-activator id="test.batch.webservice.activator" input-channel="test.batch.webservice.unmarshalling.out" ref="testServiceActivator" output-channel="test.batch.webservice.marshalling.in">
</int:service-activator>
<int-xml:marshalling-transformer input-channel="test.batch.webservice.marshalling.in" output-channel="test.batch.webservice.out" marshaller="marshaller" result-transformer="resultTransformer" />

IBatisTemplate looks as below,

<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">>
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>

You need to create the Java stubs from the XSL for that you need to run,

mvn jaxb2:xjc

It assumes that you have XSDs at location src/main/xsd location by default, refer this article for more details.

In the Test class, you need to add the Spring JUnit testing capabilities as follows,

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({
"classpath:META-INF/spring/test-webservice/test-webservice-integration-config.xml",
"classpath:config/test-webservice-beans-config-test.xml" })
public class TestIntegrationEndPointTest {

TestServiceActivator looks somewhat as below:

public class TestServiceActivator {
private static Logger logger = Logger.getLogger(TestServiceActivator.class);

@Autowired
SqlMapClientTemplate ibatisTemplate;

public TestServiceResponseType processRequest(JAXBElement element)    throws Exception {
TestServiceRequestType request = (TestServiceRequestType) element.getValue();
String status = "SUCCESS";
String type = request.getDocument().getType();
String id = request.getDocument().getId();

TestObject notifyObject = new TestObject();
notifyObject.setId(id);
notifyObject.setType(type);
ibatisTemplate.insert("testInsert", notifyObject);

TestServiceResponseType response = new TestServiceResponseType();

Document doc = new Document();
doc.setResult(status);
response.setDocument(doc);

logger.debug("Successfully saved request");

return response;
}
}

One hack we need to do is, we need to add @XmlRootElement to the stubs that were created as per this article

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TestServiceResponseType", propOrder = {
"document"
})
public class TestServiceResponseType {

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "TestServiceRequestType", propOrder = {
"document"
})
public class TestServiceRequestType {

Once you do this, we can run the mvn test and it will work. If you notice in this example, we have tested the String WS end to end using JUnit and Spring Integraiton.

For further reading refer http://blog.springsource.org/2011/01/11/spring-web-services-2-0-released/ .