Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Saga for bloc_test #3980

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

pgiacomo69
Copy link

@pgiacomo69 pgiacomo69 commented Oct 15, 2023

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

  • ✨ 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

@tenhobi tenhobi changed the title First Commit Saga for bloc_test Oct 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant