brianegan / dart_redux_epics

Redux.dart middleware for handling actions using Dart Streams
MIT License
141 stars 22 forks source link

Epics break testing when using timers #31

Closed kentcb closed 4 years ago

kentcb commented 5 years ago

Hi,

I might be missing something obvious here, but it seems to me that epics that include timers break tests, even if one explicitly tears down the store.

Here's the simplest possible repro:

void main() {
  group('repro', () {
    testWidgets('test', (tester) async {
      final store = Store<String>(
        (state, dynamic action) => state,
        initialState: 'Greetings!',
        middleware: [
          EpicMiddleware(_middleware),
        ],
      );

      final widget = StoreProvider<String>(
        store: store,
        child: MaterialApp(
          home: StoreConnector<String, String>(
            converter: (s) => s.state,
            builder: (context, state) => Text(state),
            // At least one dispatch is required, otherwise the epic is never initialized
            onInit: (store) => store.dispatch('whatever'),
          ),
        ),
      );

      await tester.pumpWidget(widget);

      await store.teardown();
    });
  });
}

Stream<dynamic> _middleware(Stream<dynamic> actions, EpicStore<String> store) => Observable<dynamic>(actions).debounceTime(aSecond).ignoreElements();

Running this gives:

_AssertionError ('package:flutter_test/src/binding.dart': Failed assertion: line 1050 pos 7: '_currentFakeAsync.nonPeriodicTimerCount == 0': A Timer is still pending even after the widget tree was disposed.)

I considered doing something like this:

Stream<dynamic> _middleware(Stream<dynamic> actions, EpicStore<String> store) =>
  Observable<dynamic>(actions)
    .debounceTime(aSecond)
    .ignoreElements()
    .takeUntil<dynamic>(Observable<dynamic>.fromFuture(actions.last));

But it appears as though the actions stream never completes either (even when tearing down the store).

Am I doing something wrong here?

kentcb commented 5 years ago

One silly little workaround I could use (just tested and it seems to work) is to define my own Teardown action that I dispatch (only from tests) when I want to bring things to an end. All relevant epics can listen for that action as a means of terminating their pipelines.

kentcb commented 5 years ago

Spoke too soon. My workaround doesn't appear to be working for Observable.periodic 🤔

brianegan commented 4 years ago

Heya @kentcb -- after a long search, the fundamental reason all of these timers are breaking has finally been tracked down and is being discussed here: https://github.com/dart-lang/sdk/issues/40131

Gonna close this out of this repo since there's really nothing I can do to fix this :/