Closed wujek-srujek closed 3 years ago
Hi @wujek-srujek π Thanks for opening an issue!
The issue is the EnvChangeSuccess
is processed before the CalculationRequested
event is added but the resulting CalculationReset
event isn't added until after. You can address that by waiting until the EnvChangeSuccess
has added the CalculationReset
event and emitted a new state before adding the second CalculationRequested
event:
blocTest<CalculationBloc, CalculationState>(
'calculation is reset when env changes and can be restarted',
build: () => calculationBloc,
act: (bloc) async {
bloc.add(CalculationRequested());
envStreamController.sink.add(EnvChangeSuccess());
await bloc.first;
bloc.add(CalculationRequested());
},
expect: [
CalculationUpdate(0),
CalculationUpdate(1),
CalculationUpdate(2),
CalculationSuccess(3),
// At this point, calculation is reset due to env change ...
CalculationInitial(),
// ... and subsequently it is restarted again due to a request.
CalculationUpdate(0),
CalculationUpdate(1),
CalculationUpdate(2),
CalculationSuccess(3),
],
);
In general, I would break this test up and just have one test which ensures that the state is reset in response to an environment change and another test to ensure that the CalculationRequested event yields the correct states.
Hope that helps π
Hi Felix, yes, I would break the test too, but it's hard - we are still at version 2.x.x of bloc
and bloc_test
(don't ask) and the seed
parameter is not yet available there for blocTest
, so it is hard to start the test at a certain state.
@wujek-srujek I completely understand. Let me know if there's anything else I can do to help π
I forgot - thank you very much for your help, yet again.
Hi Felix, Just one more question if I may. You wrote:
EnvChangeSuccess is processed before the CalculationRequested event is added but the resulting CalculationReset event isn't added until after
and it made me think. Is this because of asynchronicity and microtasks in Dart?
I decided to take a deeper dive and my take on what happens in the test is:
StreamController
, the listener of the stream is called in a 'later microtask'. It doesn't say exactly when this microtask should execute, it just says that it happens later.bloc
use plain StreamController
s under the hood. Older versions used rxdart
's PublishSubject
for events and BehaviorSubject
for states, but they both wrap StreamController
, so the rules still apply.calculationBloc.add(firstCalculationEvent); // schedules microtask #1
environmentBloc.add(environmentChangeState); // schedules microtask #2
calculationBloc.add(secondCalculationEvent); // schedules microtask #3
3 microtasks are scheduled and are processed in the scheduling sequence (i.e. #1
, then #2
, then #3
), resulting in:
#1
processes firstCalculationEvent
in CalculationBloc
's event stream listener (internal to BLoC) and (after some processing an transformations) results in a state being set on CalculationBloc
.#2
processes environmentChangeState
in the StreamController
's listener (which is actually used the backbone of the mock BLoC in my test). In this microtask, a Reset
event for CalculationBloc
is added to the bloc's event sink, which results in scheduling a stream listener microtask #4
, but it will be processed only after all microtasks scheduled until now, most importantly, after microtask #3
.#3
processes secondCalculationEvent
in CalculationBloc
's event stream listener (internal to BLoC) and (after some processing an transformations) results in the state being set on CalculationBloc
.#4
processes the Reset
event. As a result, processing of the Reset
event doesn't happen between firstCalculationEvent
and secondCalculationEvent
, but afterwards.In the application with a real EnvironmentBloc
the situation is very similar, it's just that microtask #2
will be more complex as EnvironmentBloc
will get it at the event stream end and (similar to microtask #1
and #3
in CalculationBloc
) will trigger all the BLoC event to state transformation logic.
The solution with await bloc.first
(actually, any Future
works) changes the sequence of events in that anything after the await
happens in a later event loop iteration, after any microtasks, causing the events taking place in a 'expected' sequence (from the point of view of my test).
According to this understanding described above, I successfully implemented multiple solutions with Future
s (including yours) and scheduleMicrotask
.
Can you confirm that my understanding of what happens is correct?
@wujek-srujek yes your understanding is correct and in this case adding the environmentChangeState
(microtask 2) results in adding the CalculationReset
in a later microtask (microtask 4) unless you delay adding the secondCalculationEvent
(either using futures
or scheduleMicrotask
) π
NOTE: The main, test and pubspec.yaml files are attached: sources.zip.
I have a simple BLoC called
EnvBloc
which yields new 'environments' whenever certain events happen. I also have another BLoC calledCalculationBloc
which can perform a calculation within a certain environment (the calculation may yield a few partial updates before finishing with a success). It listens toEnvBloc
and if it discovers that the environment changed, it allows the current calculation to finish (which works by default to doasyncExpand
intransformEvents
), then resets itself and subsequently allows new calculation requests. The listening part is implemented like this in the constructor:Now I would like to test this the following way:
CalculationBloc
getsCalculationRequested
and yields some states.EnvBloc
sends it aEnvChangeSuccess
state, andCalculationBloc
should reset itself. This is implemented with a mixture of a mock and aStreamController
(EnvBloc
in this question's sources is trivial but in reality it isn't and I really need to use a mock instead of a real BLoC).CalculationRequested
is sent andCalculationBloc
yields some more states.I'm trying to do it using the
blocTest
helper, but I'm failing. The sequence inact
is:but the state change in the env always comes last, i.e. my test behaves as if first two
CalculationRequested
events came, and only then theEnvChangeSuccess
. I guess it has something to do with howFutures
scheduling works in Dart, could you maybe explain what is going on if you know it?I also tried to implement a test using the standard
test
function andawait expectLater(...)
but it also fails.How can I test scenarios like this one?