I have an annotation based state machine configuration:
@Component @Scope(BeanDefinition.SCOPE_PROTOTYPE)
@EnableStateMachine(name = "machine1")
public class Machine1 extends
EnumStateMachineConfigurerAdapter<SimStates, SimEvents> {
@Override
public void configure(StateMachineStateConfigurer<SimStates, SimEvents> states) throws Exception {
states.withStates()
.initial(INIT)
.state(INIT)
.state(S1)
.state(FINISH)
.end(FINISH)
;
}
...
Now I want to create Tests for it. I would prefer not have an implicit call to getBean("machine1")
" via StateMachineFactory.getStateMachine("machine1")
, which would require an application context.
I'd rather instantiate Machine1
and feed it to some Builder, Configurator or Adapter to get a StateMachine<>
instance.
public class Machine1Test {
@Test
public void testMachine1() throws Exception {
final StateMachineConfigurer<SimStates, SimEvents> smc =
new Machine1();
final StateMachineBuilder.Builder<SimStates, SimEvents> builder =
StateMachineBuilder.builder();
// can I use the builder together with smc? Or something else?
StateMachine<SimStates,SimEvents> sm = ... // how?
}
}
Update: I updated "without full application context" to "without an implicit call to getBean("machine1")
". The question is also about understanding about all the factories, adapters, configurations and configurators of spring state machine.
I'd rather instantiate Machine1 and feed it to some Builder, Configurator or Adapter to get a StateMachine<> instance.
Spring State Mahcine supports annotation based configuration for instantiation (e.g. via Adapter) or a Builder - there's no other options.
Using @SpringBootTest(clasess = <YourEnumSMConfig>
definitely does not create a full application context:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { Machine1.class})
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class StateMachineTests {
@Autowired
private StateMachine<String, String> machine1;
@Test
public void testInitialState() throws Exception {
StatMachineTestPlan<SimState, SimEvent> plan = StateMachineTestPlanBuilder.<SimState, SimEvent>builder()
.defaultAwaitTime(2)
.stateMachine(machine1)
.step()
.expectStateChange(1)
.expectStateEntered(SimState.INITIAL)
.expectState(SimState.INITIAL)
.and()
.build()
plan.test();
}
}
Now I want to create Tests for it.
Testing with TestPlanBuilder:
There's a Testing support out of the box to test a spring state machine. It's called StateMachineTestPlan
. You can build StateMachineTestPlan
using StateMachineTestPlanBuilder
.
Access to these classes you can get from declaring the following dependency:
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-test</artifactId>
<version>2.0.3.RELEASE</version> // change version to match yours
<scope>test</scope>
</dependency>
The detailed official documentation regarding testing is available here.
I would prefer not have an implicit call to getBean("machine1")" via StateMachineFactory.getStateMachine("machine1"), which would require an application context.
Creating a SM via Builder does not require any Spring context.
public class TestEventNotAccepted {
@Test
public void testEventNotAccepted() throws Exception {
StateMachine<String, String> machine = buildMachine();
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>builder()
.defaultAwaitTime(2)
.stateMachine(machine)
.step()
.expectStates("SI")
.and()
.step()
.sendEvent("E2")
.and()
.build();
plan.test();
}
private StateMachine<String, String> buildMachine() throws Exception {
StateMachineBuilder.Builder<String, String> builder = StateMachineBuilder.builder();
builder.configureConfiguration()
.withConfiguration()
.taskExecutor(new SyncTaskExecutor())
.listener(customListener())
.autoStartup(true);
builder.configureStates()
.withStates()
.initial("SI")
.state("S1")
.state("S2");
builder.configureTransitions()
.withExternal()
.source("SI").target("S1")
.event("E1")
.action(c -> c.getExtendedState().getVariables().put("key1", "value1"))
.and()
.withExternal()
.source("S1").target("S2").event("E2");
return builder.build();
}
private StateMachineListener<String, String> customListener() {
return new StateMachineListenerAdapter<String, String>() {
@Override
public void eventNotAccepted(Message event) {
System.out.println("EVENT NOT ACCEPTED: " + event);
}
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With