bloc
bloc copied to clipboard
Saga for bloc_test
Status
IN DEVELOPMENT
Breaking Changes
NO
Description
(This pull request changes only bloc_test.dart in bloc_test package)
Testing a bloc with a complex state, can be an heavy burden, expecially when:
- the state is managed with a single class, and/or
- It has many properties/data, but not all of them are to be checked at every step, and/or
- that bloc is utilized to manage a "mutant" series of steps, like a form wizard, where the new page depends on preceding input.
The proposal: This kind of test is valid when a sequential transformer is used. I added a saga parameter to bloc_test, so a series of steps can be passed, one step can have an action or an event to the bloc, and a list of predicates to check the state changes resulting from that action/event, for example:
blocTest<CounterBloc, int>(
'Mixed Steps',
build: () => CounterBloc()..Add(CounterEvent.reset),
saga: [
Step(
outputs: [(state) => state == 0],
),
Step(
act: (bloc) => bloc.add(CounterEvent.increment),
outputs: [(state) => state == 1],
),
Step(
act: (bloc) => bloc
..add(CounterEvent.increment)
..add(CounterEvent.increment),
outputs: [(state) => state == 2,
(state) => state == 3],
),
Step(
happens: CounterEvent.increment,
outputs: [(state) => state == 4],
),
Step(
happens: CounterEvent.incrementTwoTimes,
outputs: [(state) => state ==5,(state) => state ==6,],
),
],
);
- First step: no event are added, but we only are checking if the '0' state was emitted.
- Second step: for 1 event the bloc should emits 1 state, so one predicate is present in outputs
- Third step: 2 events are passed, so the bloc should emit 2 states, that are checked by the predicates
- Fourth step: simplified sintax to add event to a bloc, whitout the need of a closure
- Fifh step: imagine that the event sent to the block generate two state transitions, those are covered by the two predicate in "outputs"
If the number of state transition is lesser than the predicates specified, the test fails.
'Step class: Defines a step of the saga tha we want to test
act
is an optional callback which will be invoked tho use methods of the with the bloc under test. In case of adding events to a bloc it's simplier to use happens
, to which an event instance can be passed. act
and happens
are mutually exclusive but both are optional, a Step
can be used to only check a state.
outputs
is a list of callbacks (bool Function(S value)
). For every callback a state is popped out, if the callback result is false the test fails.
description
A description for the Step, it will output in message, in case of failure
wait
the time to wait prior to check every output
timeOut
the maximum time to wait for states output from the bloc
class Step<B, S> {
Step(
{this.act,
this.happens,
required this.outputs,
this.description,
this.wait = const Duration(milliseconds: 50),
this.timeOut = const Duration(milliseconds: 150)}) {
assert(!(happens != null && act != null), "'act' and 'happens' can't be used at the sae time.");
}
final dynamic Function(B bloc)? act;
final Object? happens;
final List<bool Function(S value)> outputs;
final String? description;
final Duration wait;
final Duration timeOut;
}
All original test are working, so it seems that no regression are introduced. New test are also promising, because the original tests rewritten in this new form are passing without any problem.
TODO:
- Manage the case when there are more state changes than predicates in outputs, as now the step passes, but the next will probably fail;
- Complete the test suite covering this new feature;
- Include this new feature in readme.md
- general code review
I made this change to test a bloc that I'm building, designing the saga steps before writing event handlers in a sort-of TDD, I find it very useful, so I would share it. Please feel free to give me any feedback, there is alway space to learn, and any kind of comment will be appreciated.
Thank you for your work,
Type of Change
- [X] โจ New feature (non-breaking change which adds functionality)
- [ ] ๐ ๏ธ Bug fix (non-breaking change which fixes an issue)
- [ ] โ Breaking change (fix or feature that would cause existing functionality to change)
- [ ] ๐งน Code refactor
- [ ] โ Build configuration change
- [ ] ๐ Documentation
- [ ] ๐๏ธ Chore