bloc icon indicating copy to clipboard operation
bloc copied to clipboard

Saga for bloc_test

Open pgiacomo69 opened this issue 1 year ago โ€ข 0 comments

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,],
          ),
        ],
      );
  1. First step: no event are added, but we only are checking if the '0' state was emitted.
  2. Second step: for 1 event the bloc should emits 1 state, so one predicate is present in outputs
  3. Third step: 2 events are passed, so the bloc should emit 2 states, that are checked by the predicates
  4. Fourth step: simplified sintax to add event to a bloc, whitout the need of a closure
  5. 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:

  1. Manage the case when there are more state changes than predicates in outputs, as now the step passes, but the next will probably fail;
  2. Complete the test suite covering this new feature;
  3. Include this new feature in readme.md
  4. 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

pgiacomo69 avatar Oct 15 '23 21:10 pgiacomo69