Add test framework skeleton

- Relates to #49
- New project spring-statemachine-test which contains public
  classes to easy generic state machine testing.
- Concepts of StateMachineTestPlan and StateMachineTestPlanBuilder.
- Replaced one zk test to use this framework.
This commit is contained in:
Janne Valkealahti
2015-07-26 13:27:10 +01:00
parent ba6c689123
commit e92a6c5a09
10 changed files with 790 additions and 30 deletions

View File

@@ -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"

View File

@@ -1,6 +1,7 @@
rootProject.name = 'spring-statemachine'
include 'spring-statemachine-core'
include 'spring-statemachine-test'
include 'spring-statemachine-zookeeper'
include 'spring-statemachine-recipes'

View File

@@ -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();
}
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public class StateMachineTestPlan<S, E> {
private final List<StateMachine<S, E>> stateMachines;
private final List<StateMachineTestPlanStep<S, E>> steps;
/**
* Instantiates a new state machine test plan.
*
* @param stateMachines the state machines
* @param steps the steps
*/
public StateMachineTestPlan(List<StateMachine<S, E>> stateMachines, List<StateMachineTestPlanStep<S, E>> steps) {
this.stateMachines = stateMachines;
this.steps = steps;
}
/**
* Run test plan.
*
* @throws Exception the exception
*/
public void test() throws Exception {
List<LatchStateMachineListener<S, E>> listeners = new ArrayList<LatchStateMachineListener<S, E>>();
for (StateMachine<S, E> stateMachine : stateMachines) {
LatchStateMachineListener<S, E> listener = new LatchStateMachineListener<S, E>();
listeners.add(listener);
stateMachine.addStateListener(listener);
stateMachine.start();
}
for (StateMachineTestPlanStep<S, E> step : steps) {
for (LatchStateMachineListener<S, E> 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<S, E> listener : listeners) {
assertThat(listener.getStateChangedLatch().await(5, TimeUnit.SECONDS), is(true));
assertThat(listener.getStateChanged().size(), is(step.expectStateChanged));
}
}
if (step.expectState != null) {
for (StateMachine<S, E> stateMachine : stateMachines) {
assertThat(stateMachine.getState(), notNullValue());
Collection<Matcher<? super S>> itemMatchers = new ArrayList<Matcher<? super S>>();
itemMatchers.add(is(step.expectState));
assertThat(stateMachine.getState().getIds(), containsInAnyOrder(itemMatchers));
}
}
}
}
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public class StateMachineTestPlanBuilder<S, E> {
private List<StateMachine<S, E>> stateMachines = new ArrayList<StateMachine<S, E>>();
private final List<StateMachineTestPlanStep<S, E>> steps = new ArrayList<StateMachineTestPlanStep<S, E>>();
/**
* Gets a new instance of this builder.
*
* @return the state machine test plan builder
*/
public static <S, E> StateMachineTestPlanBuilder<S, E> builder() {
return new StateMachineTestPlanBuilder<S, E>();
}
/**
* Associate a state machine with this builder.
*
* @param stateMachine the state machine
* @return the state machine test plan builder
*/
public StateMachineTestPlanBuilder<S, E> stateMachine(StateMachine<S, E> 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<S, E> build() {
return new StateMachineTestPlan<S, E>(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<S, E> and() {
steps.add(new StateMachineTestPlanStep<S, E>(sendEvent, expectState, expectStateChanged));
return StateMachineTestPlanBuilder.this;
}
}
static class StateMachineTestPlanStep<S, E> {
E sendEvent;
S expectState;
Integer expectStateChanged;
public StateMachineTestPlanStep(E sendEvent, S expectState, Integer expectStateChanged) {
this.sendEvent = sendEvent;
this.expectState = expectState;
this.expectStateChanged = expectStateChanged;
}
}
}

View File

@@ -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> 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> 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> 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);
}
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public class LatchStateMachineListener<S, E> extends StateMachineListenerAdapter<S, E> {
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<StateChangedWrapper<S, E>> stateChanged = new ArrayList<StateChangedWrapper<S, E>>();
private final List<State<S, E>> stateEntered = new ArrayList<State<S, E>>();
private final List<State<S, E>> stateExited = new ArrayList<State<S, E>>();
private final List<Message<E>> eventNotAccepted = new ArrayList<Message<E>>();
private final List<Transition<S, E>> transition = new ArrayList<Transition<S, E>>();
private final List<Transition<S, E>> transitionStarted = new ArrayList<Transition<S, E>>();
private final List<Transition<S, E>> transitionEnded = new ArrayList<Transition<S, E>>();
private final List<StateMachine<S, E>> stateMachineStarted = new ArrayList<StateMachine<S, E>>();
private final List<StateMachine<S, E>> stateMachineStopped = new ArrayList<StateMachine<S, E>>();
@Override
public void stateChanged(State<S, E> from, State<S, E> to) {
synchronized (lock) {
this.stateChanged.add(new StateChangedWrapper<>(from, to));
this.stateChangedLatch.countDown();
}
}
@Override
public void stateEntered(State<S, E> state) {
synchronized (lock) {
this.stateEntered.add(state);
this.stateEnteredLatch.countDown();
}
}
@Override
public void stateExited(State<S, E> state) {
synchronized (lock) {
this.stateExited.add(state);
this.stateExitedLatch.countDown();
}
}
@Override
public void eventNotAccepted(Message<E> event) {
synchronized (lock) {
this.eventNotAccepted.add(event);
this.eventNotAcceptedLatch.countDown();
}
}
@Override
public void transition(Transition<S, E> transition) {
synchronized (lock) {
this.transition.add(transition);
this.transitionLatch.countDown();
}
}
@Override
public void transitionStarted(Transition<S, E> transition) {
synchronized (lock) {
this.transitionStarted.add(transition);
this.transitionStartedLatch.countDown();
}
}
@Override
public void transitionEnded(Transition<S, E> transition) {
synchronized (lock) {
this.transitionEnded.add(transition);
this.transitionEndedLatch.countDown();
}
}
@Override
public void stateMachineStarted(StateMachine<S, E> stateMachine) {
synchronized (lock) {
this.stateMachineStarted.add(stateMachine);
this.stateMachineStartedLatch.countDown();
}
}
@Override
public void stateMachineStopped(StateMachine<S, E> 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<StateChangedWrapper<S, E>> getStateChanged() {
return stateChanged;
}
public List<State<S, E>> getStateEntered() {
return stateEntered;
}
public List<State<S, E>> getStateExited() {
return stateExited;
}
public List<Message<E>> getEventNotAccepted() {
return eventNotAccepted;
}
public List<Transition<S, E>> getTransition() {
return transition;
}
public List<Transition<S, E>> getTransitionStarted() {
return transitionStarted;
}
public List<Transition<S, E>> getTransitionEnded() {
return transitionEnded;
}
public List<StateMachine<S, E>> getStateMachineStarted() {
return stateMachineStarted;
}
public List<StateMachine<S, E>> getStateMachineStopped() {
return stateMachineStopped;
}
public static class StateChangedWrapper<S, E> {
final State<S, E> from;
final State<S, E> to;
public StateChangedWrapper(State<S, E> from, State<S, E> to) {
this.from = from;
this.to = to;
}
}
}

View File

@@ -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<String, String> plan =
StateMachineTestPlanBuilder.<String, String>builder()
.build();
assertThat(plan, notNullValue());
List<StateMachineTestPlanStep<?, ?>> steps = TestUtils.readField("steps", plan);
assertThat(steps.size(), is(0));
}
@Test
public void testBuilderOneStep() throws Exception {
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>builder()
.step().expectState("SI").and()
.build();
assertThat(plan, notNullValue());
List<StateMachineTestPlanStep<?, ?>> steps = TestUtils.readField("steps", plan);
assertThat(steps.size(), is(1));
}
}

View File

@@ -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<String, String> machine = context.getBean(StateMachine.class);
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>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<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("SI")
.state("S1")
.state("S2");
}
@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");
}
}
}

View File

@@ -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<String, String> machine2 =
context.getBean("sm2", StateMachine.class);
TestListener listener1 =
context.getBean("listener1", TestListener.class);
TestListener listener2 =
context.getBean("listener2", TestListener.class);
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>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