# Testing flows

## Introduction

This chapter shows you how to test flows.

## Extending AbstractXmlFlowExecutionTests

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

    public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {

    }

## 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");
    }

## 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")
       };
    }

## 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.

## 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");
    }

## 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;
    }