Add test support for intermediate state changes

- Adding expectStateEntered and expectStateExited methods
  for testing state enter/exit in a given order. This gives
  on option to expect i.e. states via anonymous transitions.
- Fixes #154
This commit is contained in:
Janne Valkealahti
2016-02-07 15:05:47 +00:00
parent a72a27252c
commit 3ea89d14da
5 changed files with 116 additions and 5 deletions

View File

@@ -11,6 +11,7 @@
:core-jdbc-JdbcTemplate: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate
:sm-statecontext: http://docs.spring.io/spring-statemachine/docs/{spring-statemachine-version}/api/org/springframework/statemachine/StateContext.html
:sm-statecontext-stage: http://docs.spring.io/spring-statemachine/docs/{spring-statemachine-version}/api/org/springframework/statemachine/StateContext.Stage.html
:sm-statemachinetestplanbuilder-statemachinetestplanstepbuilder: http://docs.spring.io/spring-statemachine/docs/{spring-statemachine-version}/api/org/springframework/statemachine/test/StateMachineTestPlanBuilder.StateMachineTestPlanStepBuilder.html
:spring-security-site: http://projects.spring.io/spring-security
= Spring Statemachine - Reference Documentation

View File

@@ -1532,3 +1532,9 @@ These utilities are also used within a framework to test distributed
state machine features and multiple machines can be added to a plan.
If multiple machines are added then it is also possible to choose if
event is sent to particular, random or all machines.
[TIP]
====
All possible options for expected are documented in javadocs
{sm-statemachinetestplanbuilder-statemachinetestplanstepbuilder}[_StateMachineTestPlanStepBuilder_].
====

View File

@@ -15,11 +15,12 @@
*/
package org.springframework.statemachine.test;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
@@ -36,6 +37,7 @@ import org.apache.commons.logging.LogFactory;
import org.hamcrest.Matcher;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.test.StateMachineTestPlanBuilder.StateMachineTestPlanStep;
import org.springframework.statemachine.test.support.LatchStateMachineListener;
import org.springframework.util.StringUtils;
@@ -229,6 +231,26 @@ public class StateMachineTestPlan<S, E> {
}
}
if (!step.expectStatesEntrered.isEmpty()) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
Collection<S> states = new ArrayList<S>();
for (State<S, E> s : listener.getStateEntered()) {
states.add(s.getId());
}
assertThat(step.expectStatesEntrered, contains(states.toArray()));
}
}
if (!step.expectStatesExited.isEmpty()) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
Collection<S> states = new ArrayList<S>();
for (State<S, E> s : listener.getStateExited()) {
states.add(s.getId());
}
assertThat(step.expectStatesExited, contains(states.toArray()));
}
}
if (step.expectExtendedStateChanged != null) {
for (LatchStateMachineListener<S, E> listener : listeners.values()) {
assertThat(listener.getExtendedStateChangedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true));

View File

@@ -116,6 +116,8 @@ public class StateMachineTestPlanBuilder<S, E> {
boolean sendEventToAll = false;
boolean sendEventParallel = false;
final Collection<S> expectStates = new ArrayList<S>();
final Collection<S> expectStatesEntrered = new ArrayList<S>();
final Collection<S> expectStatesExited = new ArrayList<S>();
Integer expectStateChanged;
Integer expectStateEntered;
Integer expectStateExited;
@@ -295,6 +297,30 @@ public class StateMachineTestPlanBuilder<S, E> {
return this;
}
/**
* Expect states entered in order given.
*
* @param states the states entered
* @return the state machine test plan step builder
*/
@SuppressWarnings("unchecked")
public StateMachineTestPlanStepBuilder expectStateEntered(S... states) {
this.expectStatesEntrered.addAll(Arrays.asList(states));
return this;
}
/**
* Expect states exited in order given.
*
* @param states the states exited
* @return the state machine test plan step builder
*/
@SuppressWarnings("unchecked")
public StateMachineTestPlanStepBuilder expectStateExited(S... states) {
this.expectStatesExited.addAll(Arrays.asList(states));
return this;
}
/**
* Expect state enter happening {@code count} times.
*
@@ -433,7 +459,7 @@ public class StateMachineTestPlanBuilder<S, E> {
sendEventParallel, expectStates, expectStateChanged, expectStateEntered, expectStateExited,
expectEventNotAccepted, expectTransition, expectTransitionStarted, expectTransitionEnded,
expectStateMachineStarted, expectStateMachineStopped, expectVariableKeys, expectVariables,
expectExtendedStateChanged));
expectExtendedStateChanged, expectStatesEntrered, expectStatesExited));
return StateMachineTestPlanBuilder.this;
}
@@ -446,6 +472,8 @@ public class StateMachineTestPlanBuilder<S, E> {
boolean sendEventToAll = false;
boolean sendEventParallel = false;
final Collection<S> expectStates;
final Collection<S> expectStatesEntrered;
final Collection<S> expectStatesExited;
Integer expectStateChanged;
Integer expectStateEntered;
Integer expectStateExited;
@@ -465,7 +493,7 @@ public class StateMachineTestPlanBuilder<S, E> {
Integer expectEventNotAccepted, Integer expectTransition, Integer expectTransitionStarted,
Integer expectTransitionEnded, Integer expectStateMachineStarted, Integer expectStateMachineStopped,
Collection<Object> expectVariableKeys, Map<Object, Object> expectVariables,
Integer expectExtendedStateChanged) {
Integer expectExtendedStateChanged, Collection<S> expectStatesEntrered, Collection<S> expectStatesExited) {
this.sendEvent = sendEvent;
this.sendMessage = sendMessage;
this.sendEventMachineId = sendEventMachineId;
@@ -484,6 +512,8 @@ public class StateMachineTestPlanBuilder<S, E> {
this.expectVariableKeys = expectVariableKeys;
this.expectVariables = expectVariables;
this.expectExtendedStateChanged = expectExtendedStateChanged;
this.expectStatesEntrered = expectStatesEntrered;
this.expectStatesExited = expectStatesExited;
}
}

View File

@@ -87,6 +87,24 @@ public class StateMachineTestingTests extends AbstractStateMachineTests {
plan.test();
}
@SuppressWarnings("unchecked")
@Test
public void testIntermediate() throws Exception {
registerAndRefresh(Config4.class);
StateMachine<String, String> machine = context.getBean(StateMachine.class);
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>builder()
.stateMachine(machine)
.step().expectStateMachineStarted(1).and()
.step().expectState("SI").and()
.step().sendEvent("E1").expectStateChanged(1).expectState("S1").and()
.step().sendEvent("E2").expectStateChanged(2).expectStateEntered("S2", "S3").expectStateExited("S1", "S2").expectState("S3").and()
.build();
plan.test();
}
@Override
protected AnnotationConfigApplicationContext buildContext() {
return new AnnotationConfigApplicationContext();
@@ -194,4 +212,38 @@ public class StateMachineTestingTests extends AbstractStateMachineTests {
}
}
@Configuration
@EnableStateMachine
static class Config4 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("SI")
.state("S1")
.state("S2")
.state("S3");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("SI")
.target("S1")
.event("E1")
.and()
.withExternal()
.source("S1")
.target("S2")
.event("E2")
.and()
.withExternal()
.source("S2")
.target("S3");
}
}
}