diff --git a/build.gradle b/build.gradle index ca727b5c..3f74fc14 100644 --- a/build.gradle +++ b/build.gradle @@ -125,6 +125,19 @@ project('spring-statemachine-core') { } } +project('spring-statemachine-test') { + description = "Spring State Machine Test" + + dependencies { + compile project(":spring-statemachine-core") + + compile "org.springframework:spring-test:$springVersion" + compile "org.hamcrest:hamcrest-core:$hamcrestVersion" + compile "org.hamcrest:hamcrest-library:$hamcrestVersion" + compile "junit:junit:$junitVersion" + } +} + project('spring-statemachine-zookeeper') { description = "Spring State Machine Zookeeper" @@ -133,6 +146,7 @@ project('spring-statemachine-zookeeper') { compile "org.apache.curator:curator-recipes:$curatorVersion" compile "com.esotericsoftware.kryo:kryo:$kryoVersion" + testCompile project(":spring-statemachine-test") testCompile "org.apache.curator:curator-test:$curatorVersion" testCompile "org.springframework:spring-test:$springVersion" testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" diff --git a/settings.gradle b/settings.gradle index 4ee8d2ac..f74fef1d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name = 'spring-statemachine' include 'spring-statemachine-core' +include 'spring-statemachine-test' include 'spring-statemachine-zookeeper' include 'spring-statemachine-recipes' diff --git a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/AbstractStateMachineTests.java b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/AbstractStateMachineTests.java new file mode 100644 index 00000000..9e089051 --- /dev/null +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/AbstractStateMachineTests.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.test; + +import org.junit.After; +import org.junit.Before; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * Base helper class for state machine tests. + * + * @author Janne Valkealahti + * + */ +public abstract class AbstractStateMachineTests { + + protected AnnotationConfigApplicationContext context; + + @Before + public void setup() { + context = buildContext(); + } + + @After + public void clean() { + if (context != null) { + context.close(); + } + } + + protected AnnotationConfigApplicationContext buildContext() { + return null; + } + + protected void registerAndRefresh(Class... annotatedClasses) { + context.register(annotatedClasses); + context.refresh(); + } + +} 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 new file mode 100644 index 00000000..7dff6401 --- /dev/null +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlan.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.test; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.hamcrest.Matcher; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.test.StateMachineTestPlanBuilder.StateMachineTestPlanStep; +import org.springframework.statemachine.test.support.LatchStateMachineListener; + +/** + * {@code StateMachineTestPlan} is fully constructed plan how + * a {@link StateMachine} should be tested. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class StateMachineTestPlan { + + private final List> stateMachines; + private final List> steps; + + /** + * Instantiates a new state machine test plan. + * + * @param stateMachines the state machines + * @param steps the steps + */ + public StateMachineTestPlan(List> stateMachines, List> steps) { + this.stateMachines = stateMachines; + this.steps = steps; + } + + /** + * Run test plan. + * + * @throws Exception the exception + */ + public void test() throws Exception { + + List> listeners = new ArrayList>(); + for (StateMachine stateMachine : stateMachines) { + LatchStateMachineListener listener = new LatchStateMachineListener(); + listeners.add(listener); + stateMachine.addStateListener(listener); + stateMachine.start(); + } + + + for (StateMachineTestPlanStep step : steps) { + for (LatchStateMachineListener listener : listeners) { + listener.reset(step.expectStateChanged != null ? step.expectStateChanged : 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + if (step.sendEvent != null) { + stateMachines.get(0).sendEvent(step.sendEvent); + } + + if (step.expectStateChanged != null) { + for (LatchStateMachineListener listener : listeners) { + assertThat(listener.getStateChangedLatch().await(5, TimeUnit.SECONDS), is(true)); + assertThat(listener.getStateChanged().size(), is(step.expectStateChanged)); + } + } + + if (step.expectState != null) { + for (StateMachine stateMachine : stateMachines) { + assertThat(stateMachine.getState(), notNullValue()); + Collection> itemMatchers = new ArrayList>(); + itemMatchers.add(is(step.expectState)); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder(itemMatchers)); + } + } + } + } + +} 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 new file mode 100644 index 00000000..b1f28deb --- /dev/null +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/StateMachineTestPlanBuilder.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.test; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.statemachine.StateMachine; + +/** + * A builder for {@link StateMachineTestPlan}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class StateMachineTestPlanBuilder { + + private List> stateMachines = new ArrayList>(); + private final List> steps = new ArrayList>(); + + /** + * Gets a new instance of this builder. + * + * @return the state machine test plan builder + */ + public static StateMachineTestPlanBuilder builder() { + return new StateMachineTestPlanBuilder(); + } + + /** + * Associate a state machine with this builder. + * + * @param stateMachine the state machine + * @return the state machine test plan builder + */ + public StateMachineTestPlanBuilder stateMachine(StateMachine stateMachine) { + this.stateMachines.add(stateMachine); + return this; + } + + /** + * Gets a new step builder. + * + * @return the state machine test plan step builder + */ + public StateMachineTestPlanStepBuilder step() { + return new StateMachineTestPlanStepBuilder(); + } + + /** + * Builds the state machine test plan. + * + * @return the state machine test plan + */ + public StateMachineTestPlan build() { + return new StateMachineTestPlan(stateMachines, steps); + } + + /** + * Builder for individual plan steps. + */ + public class StateMachineTestPlanStepBuilder { + + E sendEvent; + S expectState; + Integer expectStateChanged; + + /** + * Expect a state {@code S}. + * + * @param state the state + * @return the state machine test plan step builder + */ + public StateMachineTestPlanStepBuilder expectState(S state) { + this.expectState = state; + return this; + } + + /** + * Send an event {@code E}. + * + * @param event the event + * @return the state machine test plan step builder + */ + public StateMachineTestPlanStepBuilder sendEvent(E event) { + this.sendEvent = event; + return this; + } + + /** + * Expect state changed happening {@code count} times. + * + * @param count the count + * @return the state machine test plan step builder + */ + public StateMachineTestPlanStepBuilder expectStateChanged(int count) { + if (count < 0) { + throw new IllegalArgumentException("Expected count cannot be negative, was " + count); + } + this.expectStateChanged = count; + return this; + } + + /** + * Add a new step and return {@link StateMachineTestPlanBuilder} + * for chaining. + * + * @return the state machine test plan builder for chaining + */ + public StateMachineTestPlanBuilder and() { + steps.add(new StateMachineTestPlanStep(sendEvent, expectState, expectStateChanged)); + return StateMachineTestPlanBuilder.this; + } + + } + + static class StateMachineTestPlanStep { + E sendEvent; + S expectState; + Integer expectStateChanged; + public StateMachineTestPlanStep(E sendEvent, S expectState, Integer expectStateChanged) { + this.sendEvent = sendEvent; + this.expectState = expectState; + this.expectStateChanged = expectStateChanged; + } + + } + +} diff --git a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/TestUtils.java b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/TestUtils.java new file mode 100644 index 00000000..eea91eb9 --- /dev/null +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/TestUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.test; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.springframework.util.ReflectionUtils; + +/** + * Utils for tests. + * + * @author Janne Valkealahti + * + */ +public class TestUtils { + + @SuppressWarnings("unchecked") + public static T readField(String name, Object target) throws Exception { + Field field = null; + Class clazz = target.getClass(); + do { + try { + field = clazz.getDeclaredField(name); + } catch (Exception ex) { + } + + clazz = clazz.getSuperclass(); + } while (field == null && !clazz.equals(Object.class)); + + if (field == null) + throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " + + target.getClass()); + field.setAccessible(true); + return (T) field.get(target); + } + + @SuppressWarnings("unchecked") + public static T callMethod(String name, Object target) throws Exception { + Class clazz = target.getClass(); + Method method = ReflectionUtils.findMethod(clazz, name); + + if (method == null) + throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " + + target.getClass()); + method.setAccessible(true); + return (T) ReflectionUtils.invokeMethod(method, target); + } + + public static void setField(String name, Object target, Object value) throws Exception { + Field field = null; + Class clazz = target.getClass(); + do { + try { + field = clazz.getDeclaredField(name); + } catch (Exception ex) { + } + + clazz = clazz.getSuperclass(); + } while (field == null && !clazz.equals(Object.class)); + + if (field == null) + throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of " + + target.getClass()); + field.setAccessible(true); + field.set(target, value); + } + + @SuppressWarnings("unchecked") + public static T callMethod(String name, Object target, Object[] args, Class[] argsTypes) throws Exception { + Class clazz = target.getClass(); + Method method = ReflectionUtils.findMethod(clazz, name, argsTypes); + + if (method == null) + throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of " + + target.getClass()); + method.setAccessible(true); + return (T) ReflectionUtils.invokeMethod(method, target, args); + } + +} diff --git a/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/support/LatchStateMachineListener.java b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/support/LatchStateMachineListener.java new file mode 100644 index 00000000..7e1df2b7 --- /dev/null +++ b/spring-statemachine-test/src/main/java/org/springframework/statemachine/test/support/LatchStateMachineListener.java @@ -0,0 +1,242 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.test.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import org.springframework.messaging.Message; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.listener.StateMachineListener; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; + +/** + * A {@link StateMachineListener} which is used during the tests + * to assert correct count of listener callbacks. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class LatchStateMachineListener extends StateMachineListenerAdapter { + + private final Object lock = new Object(); + + private volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); + private volatile CountDownLatch stateEnteredLatch = new CountDownLatch(1); + private volatile CountDownLatch stateExitedLatch = new CountDownLatch(1); + private volatile CountDownLatch eventNotAcceptedLatch = new CountDownLatch(1); + private volatile CountDownLatch transitionLatch = new CountDownLatch(1); + private volatile CountDownLatch transitionStartedLatch = new CountDownLatch(1); + private volatile CountDownLatch transitionEndedLatch = new CountDownLatch(1); + private volatile CountDownLatch stateMachineStartedLatch = new CountDownLatch(1); + private volatile CountDownLatch stateMachineStoppedLatch = new CountDownLatch(1); + + private final List> stateChanged = new ArrayList>(); + private final List> stateEntered = new ArrayList>(); + private final List> stateExited = new ArrayList>(); + private final List> eventNotAccepted = new ArrayList>(); + private final List> transition = new ArrayList>(); + private final List> transitionStarted = new ArrayList>(); + private final List> transitionEnded = new ArrayList>(); + private final List> stateMachineStarted = new ArrayList>(); + private final List> stateMachineStopped = new ArrayList>(); + + @Override + public void stateChanged(State from, State to) { + synchronized (lock) { + this.stateChanged.add(new StateChangedWrapper<>(from, to)); + this.stateChangedLatch.countDown(); + } + } + + @Override + public void stateEntered(State state) { + synchronized (lock) { + this.stateEntered.add(state); + this.stateEnteredLatch.countDown(); + } + } + + @Override + public void stateExited(State state) { + synchronized (lock) { + this.stateExited.add(state); + this.stateExitedLatch.countDown(); + } + } + + @Override + public void eventNotAccepted(Message event) { + synchronized (lock) { + this.eventNotAccepted.add(event); + this.eventNotAcceptedLatch.countDown(); + } + } + + @Override + public void transition(Transition transition) { + synchronized (lock) { + this.transition.add(transition); + this.transitionLatch.countDown(); + } + } + + @Override + public void transitionStarted(Transition transition) { + synchronized (lock) { + this.transitionStarted.add(transition); + this.transitionStartedLatch.countDown(); + } + } + + @Override + public void transitionEnded(Transition transition) { + synchronized (lock) { + this.transitionEnded.add(transition); + this.transitionEndedLatch.countDown(); + } + } + + @Override + public void stateMachineStarted(StateMachine stateMachine) { + synchronized (lock) { + this.stateMachineStarted.add(stateMachine); + this.stateMachineStartedLatch.countDown(); + } + } + + @Override + public void stateMachineStopped(StateMachine stateMachine) { + synchronized (lock) { + this.stateMachineStopped.add(stateMachine); + this.stateMachineStoppedLatch.countDown(); + } + } + + public void reset(int stateChangedCount, int stateEnteredCount, int stateExitedCount, int eventNotAcceptedCount, + int transitionCount, int transitionStartedCount, int transitionEndedCount, int stateMachineStartedCount, + int stateMachineStoppedCount) { + synchronized (lock) { + this.stateChangedLatch = new CountDownLatch(stateChangedCount); + this.stateEnteredLatch = new CountDownLatch(stateEnteredCount); + this.stateExitedLatch = new CountDownLatch(stateExitedCount); + this.eventNotAcceptedLatch = new CountDownLatch(eventNotAcceptedCount); + this.transitionLatch = new CountDownLatch(transitionCount); + this.transitionStartedLatch = new CountDownLatch(transitionStartedCount); + this.transitionEndedLatch = new CountDownLatch(transitionEndedCount); + this.stateMachineStartedLatch = new CountDownLatch(stateMachineStartedCount); + this.stateMachineStoppedLatch = new CountDownLatch(stateMachineStoppedCount); + this.stateChanged.clear(); + this.stateEntered.clear(); + this.stateExited.clear(); + this.eventNotAccepted.clear(); + this.transition.clear(); + this.transitionStarted.clear(); + this.transitionEnded.clear(); + this.stateMachineStarted.clear(); + this.stateMachineStopped.clear(); + } + } + + public CountDownLatch getStateChangedLatch() { + return stateChangedLatch; + } + + public CountDownLatch getStateEnteredLatch() { + return stateEnteredLatch; + } + + public CountDownLatch getStateExitedLatch() { + return stateExitedLatch; + } + + public CountDownLatch getEventNotAcceptedLatch() { + return eventNotAcceptedLatch; + } + + public CountDownLatch getTransitionLatch() { + return transitionLatch; + } + + public CountDownLatch getTransitionStartedLatch() { + return transitionStartedLatch; + } + + public CountDownLatch getTransitionEndedLatch() { + return transitionEndedLatch; + } + + public CountDownLatch getStateMachineStartedLatch() { + return stateMachineStartedLatch; + } + + public CountDownLatch getStateMachineStoppedLatch() { + return stateMachineStoppedLatch; + } + + public List> getStateChanged() { + return stateChanged; + } + + public List> getStateEntered() { + return stateEntered; + } + + public List> getStateExited() { + return stateExited; + } + + public List> getEventNotAccepted() { + return eventNotAccepted; + } + + public List> getTransition() { + return transition; + } + + public List> getTransitionStarted() { + return transitionStarted; + } + + public List> getTransitionEnded() { + return transitionEnded; + } + + public List> getStateMachineStarted() { + return stateMachineStarted; + } + + public List> getStateMachineStopped() { + return stateMachineStopped; + } + + public static class StateChangedWrapper { + final State from; + final State to; + + public StateChangedWrapper(State from, State to) { + this.from = from; + this.to = to; + } + + } + +} diff --git a/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestPlanBuilderTests.java b/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestPlanBuilderTests.java new file mode 100644 index 00000000..6eb8e023 --- /dev/null +++ b/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestPlanBuilderTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.test; + +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.util.List; + +import org.junit.Test; +import org.springframework.statemachine.test.StateMachineTestPlanBuilder.StateMachineTestPlanStep; + +public class StateMachineTestPlanBuilderTests { + + @Test + public void testBuilderNoSteps() throws Exception { + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .build(); + assertThat(plan, notNullValue()); + List> steps = TestUtils.readField("steps", plan); + assertThat(steps.size(), is(0)); + } + + @Test + public void testBuilderOneStep() throws Exception { + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .step().expectState("SI").and() + .build(); + assertThat(plan, notNullValue()); + List> steps = TestUtils.readField("steps", plan); + assertThat(steps.size(), is(1)); + } + +} 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 new file mode 100644 index 00000000..291f0970 --- /dev/null +++ b/spring-statemachine-test/src/test/java/org/springframework/statemachine/test/StateMachineTestingTests.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.test; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +public class StateMachineTestingTests extends AbstractStateMachineTests { + + @SuppressWarnings("unchecked") + @Test + public void testSimpleTestingConcept() throws Exception { + registerAndRefresh(Config1.class); + StateMachine machine = context.getBean(StateMachine.class); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(machine) + .step().expectState("SI").and() + .step().sendEvent("E1").expectStateChanged(1).expectState("S1").and() + .step().sendEvent("E2").expectStateChanged(1).expectState("S2").and() + .build(); + + plan.test(); + } + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @Configuration + @EnableStateMachine + static class Config1 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial("SI") + .state("S1") + .state("S2"); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source("SI") + .target("S1") + .event("E1") + .and() + .withExternal() + .source("S1") + .target("S2") + .event("E2"); + } + + } + +} diff --git a/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java b/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java index d42de2fd..141ae63a 100644 --- a/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java +++ b/spring-statemachine-zookeeper/src/test/java/org/springframework/statemachine/zookeeper/ZookeeperStateMachineTests.java @@ -39,6 +39,8 @@ import org.springframework.statemachine.ensemble.DistributedStateMachine; import org.springframework.statemachine.ensemble.StateMachineEnsemble; import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.statemachine.state.State; +import org.springframework.statemachine.test.StateMachineTestPlan; +import org.springframework.statemachine.test.StateMachineTestPlanBuilder; import org.springframework.statemachine.transition.Transition; public class ZookeeperStateMachineTests extends AbstractZookeeperTests { @@ -137,37 +139,16 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests { StateMachine machine2 = context.getBean("sm2", StateMachine.class); - TestListener listener1 = - context.getBean("listener1", TestListener.class); - TestListener listener2 = - context.getBean("listener2", TestListener.class); + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(machine1) + .stateMachine(machine2) + .step().expectState("SI").and() + .step().sendEvent("E1").expectStateChanged(1).expectState("S1").and() + .step().sendEvent("E2").expectStateChanged(1).expectState("S2").and() + .build(); - assertThat(listener1.stateMachineStartedLatch.await(1, TimeUnit.SECONDS), is(true)); - assertThat(listener2.stateMachineStartedLatch.await(1, TimeUnit.SECONDS), is(true)); - - assertThat(machine1.getState().getIds(), containsInAnyOrder("SI")); - assertThat(machine2.getState().getIds(), containsInAnyOrder("SI")); - - listener1.reset(1); - listener2.reset(1); - machine1.sendEvent("E1"); - - assertThat(listener1.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true)); - assertThat(listener1.stateChangedCount, is(1)); - assertThat(listener2.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true)); - assertThat(listener2.stateChangedCount, is(1)); - assertThat(machine1.getState().getIds(), containsInAnyOrder("S1")); - assertThat(machine2.getState().getIds(), containsInAnyOrder("S1")); - - listener1.reset(1); - listener2.reset(1); - machine1.sendEvent("E2"); - assertThat(listener1.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true)); - assertThat(listener1.stateChangedCount, is(1)); - assertThat(listener2.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true)); - assertThat(listener2.stateChangedCount, is(1)); - assertThat(machine1.getState().getIds(), containsInAnyOrder("S2")); - assertThat(machine2.getState().getIds(), containsInAnyOrder("S2")); + plan.test(); } @Test