# 14. Testing flows ## 14.1. Introduction This chapter shows you how to test flows. ## 14.2. Extending AbstractXmlFlowExecutionTests To test the execution of a XML-based flow definition, extend `AbstractXmlFlowExecutionTests`: ``` public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests { } ``` ## 14.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"); } ``` ## 14.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") }; } ``` ## 14.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. ## 14.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"); } ``` ## 14.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; } ```