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

stream.firstOrNull with timeout confusing behavior #2317

Open
Conrad33 opened this issue May 1, 2024 · 0 comments
Open

stream.firstOrNull with timeout confusing behavior #2317

Conrad33 opened this issue May 1, 2024 · 0 comments

Comments

@Conrad33
Copy link

Conrad33 commented May 1, 2024

I have stumbled across a difference in behavior between stream.first and stream.firstOrNull when using timeouts with fakeAsync:

  // Passes as expected
  test('stream.first with timeout', () async {
    fakeAsync((FakeAsync async) {
      expect(
        Stream<int>.value(0).first.timeout(const Duration(seconds: 1)),
        completion(0),
      );
      async.elapse(const Duration(seconds: 10));
    });
  });

  // Fails with timeout exception
  test('stream.firstOrNull with timeout', () async {
    fakeAsync((FakeAsync async) {
      expect(
        Stream<int>.value(0).firstOrNull.timeout(const Duration(seconds: 1)),
        completion(0),
      );
      async.elapse(const Duration(seconds: 10));
    });
  });

I would expect these tests to behave identically given that both streams are guaranteed to have a value. Could this be the intended behavior? My main concern is that fakeAsync is sensitive to the implementation details of methods that otherwise behave identically (in this case because the stream has a value). This would make refactoring tested code a real pain. But maybe I am missing something.

I only ran into this issue while writing tests with fakeAsync. The actual code being tested, which uses firstOrNull, seems to work fine while debugging, but the tests fail because of the timeout.

As a workaround, I am using a custom firstOrNull with a slightly different implementation that makes it work like I would expect:

extension FirstOrNull2Extension<T> on Stream<T> {
  Future<T?> get firstOrNull2 async {
    final Completer<T?> completer = Completer<T?>.sync();
    final StreamSubscription<T> subscription = listen(
      completer.complete,
      onError: completer.completeError,
      onDone: completer.complete,
      cancelOnError: true,
    );
    return completer.future.whenComplete(subscription.cancel);
  }
}

(Not sure if this mimics firstOrNull 100%, but dropping it in my code fixed my issues)

My first attempt at an alternate firstOrNull implementation gave me the same problem when applying timeouts in a fakeAsync:

extension FirstOrNull2Extension<T> on Stream<T> {
  Future<T?> get firstOrNull2 async {
    await for (final T event in this) {
      return event;
    }
    return null;
   }
}
@mosuem mosuem transferred this issue from dart-archive/fake_async Oct 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants