Get busy living or get busy dying.

Summary

To run integration and unit test Spring State Machine, you can leverage Spring State Machine’s StateMachineTestPlan and StateMachineTestPlanBuilder.

Firstly, let’s take a look at one full example for this testing

Code sample

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.test.StateMachineTestPlan;
import org.springframework.statemachine.test.StateMachineTestPlanBuilder;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
 
 
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {YourOwnStateMachineConfig.class},
        properties = "spring.main.allow-bean-definition-overriding=true")
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@EnableStateMachineFactory(contextEvents = false)
public class TestStateMachine {
 
    @MockBean
    private StateMachineLoggingListener stateMachineLoggingListener;
 
    @Autowired
    private StateMachineFactory<YourStateDto, YourEventDto> stateMachineFactory;
 
    private StateMachine<YourStateDto, YourEventDto> stateMachine;
 
    @Before
    public void setup() throws Exception {
        stateMachine = stateMachineFactory.getStateMachine();
        // plan don't know how to wait if machine is started
        // automatically so wait here.
        for (int i = 0; i < 10; i++) {
            if (stateMachine.getState() != null) {
                break;
            } else {
                Thread.sleep(200);
            }
        }
    }
 
    @Test
    public void shouldMoveForwardInGoodPath() throws Exception {
        StateMachineTestPlan<YourStateDto, YourEventDto> plan =
                StateMachineTestPlanBuilder.<YourStateDto, YourEventDto>builder()
                        .stateMachine(stateMachine)
                        .step()
                        .expectState(STATE_INIT)
                        .and()
                        .step()
                        .sendEvent(EVENT_2ND_STEP)
                        .sendEvent(EVENT_3RD_STEP)
                        .sendEvent(EVENT_4TH_STEP)
                        .expectState(STATE_COMPLETED)
                        .and()
                        .build();
        plan.test();
    }
 
 
 
    @Test
    public void shouldMoveToEndInTheMiddleOfFlow() throws Exception {
        StateMachineTestPlan<YourStateDto, YourEventDto> plan =
                StateMachineTestPlanBuilder.<YourStateDto, YourEventDto>builder()
                        .stateMachine(stateMachine)
                        .step()
                        .expectState(STATE_INIT)
                        .and()
                        .step()
                        .sendEvent(EVENT_2ND_STEP)
                        .expectState(STATE_MIDDLE)
                        .and()
                        .step()
                        .sendEvent(EVENT_CLOSE_STEP)
                        .expectState(STATE_COMPLETED)
                        .and()
                        .build();
        plan.test();
    }
 
    @Test
    public void shouldRejectWorkFlow() throws Exception {
        StateMachineTestPlan<YourStateDto, YourEventDto> plan =
                StateMachineTestPlanBuilder.<YourStateDto, YourEventDto>builder()
                        .stateMachine(stateMachine)
                        .step()
                        .sendEvent(EVENT_2ND_STEP)
                        .sendEvent(EVENT_3RD_STEP)
                        .sendEvent(EVENT_4TH_STEP)
                        .expectState(STATE_MIDDLE)
                        .and()
                        .step()
                        .sendEvent(PA_E_REJECT_SOA)
                        .expectState(STATE_MIDDLE_MORE)
                        .and()
                        .step()
                        .sendEvent(EVENT_4TH_STEP)
                        .sendEvent(EVENT_5TH_STEP)
                        .expectState(STATE_REJECTED)
                        .and()
                        .build();
        plan.test();
    }
}

Instructions

To test a statemachine, firstly you’d using following annotations:

  • @RunWith(SpringRunner.class) : to run this test with Spring integrated Junit runner
  • @SpringBootTest(classes = {YourOwnStateMachineConfig.class} : this shoudl assign the Java class with @Configuraion and defines your state machine state/transition/events.

Secondly, you’d autowire a bean about the StateMahineFactory which will be created automatically .

@Autowired private StateMachineFactory<YourStateDto, YourEventDto> stateMachineFactory;

This StateMachineFactory is one ObjectStateMachineFactory within package org.springframework.statemachine.config;

This is created during afterPropertiesSet method within StateMachineFactoryConfiguration.

Details as: org\springframework\statemachine\config\configuration\StateMachineFactoryConfiguration.java

So it means if you put Spring StateMachine library/binary into your class path (e.g. include spring state machine in pom.xml), it will be executed automatically.

As below code snippet

@Override
		public void afterPropertiesSet() throws Exception {

-HTH-

Updated: