From 3ea89d14daf42d78b21f0b96aaa294ecb6bbacb6 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sun, 7 Feb 2016 15:05:47 +0000 Subject: [PATCH] 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 --- docs/src/reference/asciidoc/index.adoc | 1 + docs/src/reference/asciidoc/sm.adoc | 6 +++ .../test/StateMachineTestPlan.java | 28 ++++++++-- .../test/StateMachineTestPlanBuilder.java | 34 +++++++++++- .../test/StateMachineTestingTests.java | 52 +++++++++++++++++++ 5 files changed, 116 insertions(+), 5 deletions(-) diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc index 37eea9c0..54eb44cb 100644 --- a/docs/src/reference/asciidoc/index.adoc +++ b/docs/src/reference/asciidoc/index.adoc @@ -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 diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 546fe331..95cda945 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -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_]. +==== diff --git a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java index a19996d3..7c9a3c80 100644 --- a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java @@ -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 { } } + if (!step.expectStatesEntrered.isEmpty()) { + for (LatchStateMachineListener listener : listeners.values()) { + Collection states = new ArrayList(); + for (State s : listener.getStateEntered()) { + states.add(s.getId()); + } + assertThat(step.expectStatesEntrered, contains(states.toArray())); + } + } + + if (!step.expectStatesExited.isEmpty()) { + for (LatchStateMachineListener listener : listeners.values()) { + Collection states = new ArrayList(); + for (State s : listener.getStateExited()) { + states.add(s.getId()); + } + assertThat(step.expectStatesExited, contains(states.toArray())); + } + } + if (step.expectExtendedStateChanged != null) { for (LatchStateMachineListener listener : listeners.values()) { assertThat(listener.getExtendedStateChangedLatch().await(defaultAwaitTime, TimeUnit.SECONDS), is(true)); diff --git a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java index e5700d05..2aa8449c 100644 --- a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java @@ -116,6 +116,8 @@ public class StateMachineTestPlanBuilder { boolean sendEventToAll = false; boolean sendEventParallel = false; final Collection expectStates = new ArrayList(); + final Collection expectStatesEntrered = new ArrayList(); + final Collection expectStatesExited = new ArrayList(); Integer expectStateChanged; Integer expectStateEntered; Integer expectStateExited; @@ -295,6 +297,30 @@ public class StateMachineTestPlanBuilder { 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 { 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 { boolean sendEventToAll = false; boolean sendEventParallel = false; final Collection expectStates; + final Collection expectStatesEntrered; + final Collection expectStatesExited; Integer expectStateChanged; Integer expectStateEntered; Integer expectStateExited; @@ -465,7 +493,7 @@ public class StateMachineTestPlanBuilder { Integer expectEventNotAccepted, Integer expectTransition, Integer expectTransitionStarted, Integer expectTransitionEnded, Integer expectStateMachineStarted, Integer expectStateMachineStopped, Collection expectVariableKeys, Map expectVariables, - Integer expectExtendedStateChanged) { + Integer expectExtendedStateChanged, Collection expectStatesEntrered, Collection expectStatesExited) { this.sendEvent = sendEvent; this.sendMessage = sendMessage; this.sendEventMachineId = sendEventMachineId; @@ -484,6 +512,8 @@ public class StateMachineTestPlanBuilder { this.expectVariableKeys = expectVariableKeys; this.expectVariables = expectVariables; this.expectExtendedStateChanged = expectExtendedStateChanged; + this.expectStatesEntrered = expectStatesEntrered; + this.expectStatesExited = expectStatesExited; } } diff --git a/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestingTests.java b/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestingTests.java index 56fd5571..b8028121 100644 --- a/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestingTests.java +++ b/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestingTests.java @@ -87,6 +87,24 @@ public class StateMachineTestingTests extends AbstractStateMachineTests { plan.test(); } + @SuppressWarnings("unchecked") + @Test + public void testIntermediate() throws Exception { + registerAndRefresh(Config4.class); + StateMachine machine = context.getBean(StateMachine.class); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.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 { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .state("S1") + .state("S2") + .state("S3"); + } + + @Override + public void configure(StateMachineTransitionConfigurer 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"); + } + + } + }