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