testing.md 3.8 KB
Newer Older
茶陵後's avatar
茶陵後 已提交
1 2
# 14. Testing flows

茶陵後's avatar
茶陵後 已提交
3
## 14.1. Introduction
茶陵後's avatar
茶陵後 已提交
4 5 6

This chapter shows you how to test flows.

茶陵後's avatar
茶陵後 已提交
7
## 14.2. Extending AbstractXmlFlowExecutionTests
茶陵後's avatar
茶陵後 已提交
8 9 10 11 12 13 14 15 16 17

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

```
public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {

}
		
```

茶陵後's avatar
茶陵後 已提交
18
## 14.3. Specifying the path to the flow to test
茶陵後's avatar
茶陵後 已提交
19 20 21 22 23 24 25 26 27 28 29

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

茶陵後's avatar
茶陵後 已提交
30
## 14.4. Registering flow dependencies
茶陵後's avatar
茶陵後 已提交
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

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

茶陵後's avatar
茶陵後 已提交
56
## 14.5. Testing flow startup
茶陵後's avatar
茶陵後 已提交
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

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.

茶陵後's avatar
茶陵後 已提交
79
## 14.6. Testing flow event handling
茶陵後's avatar
茶陵後 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

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

茶陵後's avatar
茶陵後 已提交
101
## 14.7. Mocking a subflow
茶陵後's avatar
茶陵後 已提交
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

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