<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
	"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="testing">
	<title>Testing flows</title>
	<sect1 id="testing-introduction">
		<title>Introduction</title>
		<para>
			This chapter shows you how to test flows.
		</para>
	</sect1>
	<sect1 id="extending-abstractflowexecutiontest">
		<title>Extending AbstractXmlFlowExecutionTests</title>
		<para>
			To test the execution of a XML-based flow definition, extend <code>AbstractXmlFlowExecutionTests</code>:
		</para>
		<programlisting language="java"><![CDATA[
public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {

}]]>
		</programlisting>
	</sect1>
	<sect1 id="override-getResource">
		<title>Specifying the path to the flow to test</title>
		<para>
			At a minimum, you must override <code>getResource(FlowDefinitionResourceFactory)</code> to return the path to the flow you wish to test:
		</para>
		<programlisting language="java"><![CDATA[
@Override
protected FlowDefinitionResource getResource(FlowDefinitionResourceFactory resourceFactory) {
    return resourceFactory.createFileResource("src/main/webapp/WEB-INF/hotels/booking/booking.xml");
}]]>
		</programlisting>
	</sect1>
	<sect1 id="override-configureFlowBuilderContext">
		<title>Registering flow dependencies</title>
		<para>
			If your flow has dependencies on externally managed services,
			also override <code>configureFlowBuilderContext(MockFlowBuilderContext)</code> to register stubs or mocks of those services:
		</para>
		<programlisting language="java"><![CDATA[
@Override
protected void configureFlowBuilderContext(MockFlowBuilderContext builderContext) {
    builderContext.registerBean("bookingService", new StubBookingService());
}]]>
        </programlisting>
        <para>
        	If your flow extends from another flow, or has states that extend other states,
        	also override <code>getModelResources(FlowDefinitionResourceFactory)</code> to return the path to the parent flows.
        </para>
		<programlisting language="java"><![CDATA[
@Override
protected FlowDefinitionResource[] getModelResources(FlowDefinitionResourceFactory resourceFactory) {
   return new FlowDefinitionResource[] {
       resourceFactory.createFileResource("src/main/webapp/WEB-INF/common/common.xml")
   };
}]]>
		</programlisting>
	</sect1>
	<sect1 id="testing-flowstartup">
		<title>Testing flow startup</title>
        <para>
        	Have your first test exercise the startup of your flow:
        </para>
		<programlisting language="java"><![CDATA[
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);
}]]>
        </programlisting>
        <para>
        	Assertions generally verify the flow is in the correct state you expect.
        </para>
	</sect1>
	<sect1 id="testing-flowevents">
		<title>Testing flow event handling</title>	
        <para>
        	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 <code>setCurrentState(String)</code> method to jump to the flow state where you wish to begin your test.
        </para>
        <programlisting language="java"><![CDATA[
public void testEnterBookingDetails_Proceed() {

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

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

    assertCurrentStateEquals("reviewBooking");
}]]>
		</programlisting>
	</sect1>
	<sect1 id="testing-mockingsubflows">
        <title>Mocking a subflow</title>
        <para>
        	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.
        </para>
		<programlisting language="java"><![CDATA[
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;
}]]>
		</programlisting>
	</sect1>
</chapter>