-
Hi! First of all, thanks for maintaining I was wondering, how one would best go about testing objects that implement a state machine? I tried using This is an example test code I tried: // this is all inside a fake_async block
test("transitions back to unregistered after 10 minutes", () async {
final completer = Completer<void>();
final accountFuture = Account.create();
accountFuture.then((account) {
final registerFuture = account.register();
registerFuture.then((_) {
// By calling .register(), the state machine will transition to .registered
// The .registered state has a timeout transition of 10 minutes, after
// which it will transition back to .unregistered
// This fails, as the state is still .registered
expect(account.status, equals(AccountStatus.unregistered));
a.elapse(const Duration(minutes: 100));
// This fails, as the state is still .registered, despite 100 minutes having passed(?)
expect(account.status, equals(AccountStatus.unregistered));
completer.complete();
});
});
// Prevent test from exiting early
await completer.future;
}); Any help would be greatly appreciated, as the plugin is perfect for building complex, time sensitive state machines but testing them has yielded no usable results for me after hours of trying. |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
One thing that looks suspicious is the position of the comment Otherwise I have a hard time understanding what your test is attempting to do. A minimal reproducible example would help me debug the problem and propose a solution. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the reply. I did create a minimal example for testing and it works fine - unlike my actual code. I will close this and re-open if I find any issue actually related to state-machine. For anyone else stumbling upon this, this is my working example code: // Class file
import 'package:statemachine/statemachine.dart';
class TestEntity {
final TestEntityState _state = TestEntityState();
TestEntity() {
this._state.registered.onTimeout(const Duration(minutes: 10), () {
this._state.unregistered.enter();
});
this._state.machine.start();
this._state.unregistered.enter();
}
void register() {
this._state.registered.enter();
}
TestEntityStatus get currentState {
return this._state.currentState;
}
}
enum TestEntityStatus {
unknown,
unregistered,
registered,
}
class TestEntityState {
Machine machine = Machine<TestEntityStatus>();
late State unknown;
late State unregistered;
late State registered;
TestEntityState() {
this.unknown = this.machine.newState(TestEntityStatus.unknown);
this.unregistered = this.machine.newState(TestEntityStatus.unregistered);
this.registered = this.machine.newState(TestEntityStatus.registered);
}
TestEntityStatus get currentState {
final stateObj = this.machine.current as State<TestEntityStatus>?;
return stateObj?.identifier ?? TestEntityStatus.unknown;
}
} // Test file
import 'package:fake_async/fake_async.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kochmobile/entities/test_entity.dart';
void main() {
test("enters default state on creation", () {
final testEntity = TestEntity();
expect(testEntity.currentState, TestEntityStatus.unregistered);
});
test("switches to registered when calling function", () {
final testEntity = TestEntity();
testEntity.register();
expect(testEntity.currentState, TestEntityStatus.registered);
});
test("switches back to unregistered after timeout", () {
fakeAsync((a) {
final testEntity = TestEntity();
testEntity.register();
expect(testEntity.currentState, TestEntityStatus.registered);
a.elapse(const Duration(minutes: 12));
expect(testEntity.currentState, TestEntityStatus.unregistered);
});
});
} |
Beta Was this translation helpful? Give feedback.
-
In case anyone finds this helpful, I since figured out what happened... I was using State.onStream for multiple states in my state machine and I used the following code in test files mockAccountService = MockAccountService();
StreamController<NativeEvent>? accountEvents;
setUp(() {
reset(mockAccountService);
accountEvents = StreamController<NativeEvent>(sync: true);
when(() => mockAccountService.events).thenAnswer((_) => accountEvents!.stream);
});
tearDown(() {
accountEvents!.close();
accountEvents = null;
});
test("...", () {
// can now simply use accountEvents!.add(); to simulate inputs via stream
}); Now, since This in turn caused TransitionError on all states except the ones created before the second State.onStream, since they never got set up. Changing the dummy stream to a accountEvents = StreamController<NativeEvent>.broadcast(sync: true); |
Beta Was this translation helpful? Give feedback.
In case anyone finds this helpful, I since figured out what happened...
I was using State.onStream for multiple states in my state machine and I used the following code in test files
main()
function:Now, since
accountEvents
…