15. Testing flows

15.1. Introduction

This chapter shows you how to test flows.

15.2. Extending AbstractXmlFlowExecutionTests

To test the execution of a XML-based flow definition, extend AbstractXmlFlowExecutionTests:

public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {

}
		

15.3. Specifying the path to the flow to test

At a minimum, you must override getResource(FlowDefinitionResourceFactory) to return the path to the flow you wish to test:

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

15.4. Registering flow dependencies

If your flow has dependencies on externally managed services, also override configureFlowBuilderContext(MockFlowBuilderContext) to register stubs or mocks of those services:

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

If your flow extends from another flow, or has states that extend other states, also override getModelResources(FlowDefinitionResourceFactory) to return the path to the parent flows.

@Override
protected FlowDefinitionResource[] getModelResources(FlowDefinitionResourceFactory resourceFactory) {
return new FlowDefinitionResource[] {
	   resourceFactory.createFileResource("src/main/webapp/WEB-INF/common/common.xml")
};
}
		

15.5. Testing flow startup

Have your first test exercise the startup of your flow:

public void testStartBookingFlow() {

	Booking booking = createTestBooking();

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

	assertCurrentStateEquals("enterBookingDetails");
	assertTrue(getRequiredFlowAttribute("booking") instanceof Booking);
}
		

Assertions generally verify the flow is in the correct state you expect.

15.6. Testing flow event handling

Define additional tests to exercise flow event handling behavior. You goal should be to exercise all paths through the flow. You can use the convenient setCurrentState(String) method to jump to the flow state where you wish to begin your test.

public void testEnterBookingDetails_Proceed() {

	setCurrentState("enterBookingDetails");

	getFlowScope().put("booking", createTestBooking());

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

	assertCurrentStateEquals("reviewBooking");
}
		

15.7. Mocking a subflow

To test calling a subflow, register a mock implementation of the subflow that asserts input was passed in correctly and returns the correct outcome for your test scenario.

public void testBookHotel() {

	setCurrentState("reviewHotel");

	Hotel hotel = new Hotel();
	hotel.setId(1L);
	hotel.setName("Jameson Inn");
	getFlowScope().put("hotel", hotel);

	getFlowDefinitionRegistry().registerFlowDefinition(createMockBookingSubflow());

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

	// verify flow ends on 'bookingConfirmed'
	assertFlowExecutionEnded();
	assertFlowExecutionOutcomeEquals("finish");
}

public Flow createMockBookingSubflow() {
	Flow mockBookingFlow = new Flow("booking");
	mockBookingFlow.setInputMapper(new Mapper() {
		public MappingResults map(Object source, Object target) {
			// assert that 1L was passed in as input
			assertEquals(1L, ((AttributeMap) source).get("hotelId"));
			return null;
		}
	});
	// immediately return the bookingConfirmed outcome so the caller can respond
	new EndState(mockBookingFlow, "bookingConfirmed");
	return mockBookingFlow;
}