dart-lang / test

A library for writing unit tests in Dart.
https://pub.dev/packages/test
495 stars 213 forks source link

Prevent accidental misuse of fake_async when using async/await #2307

Open tdanyluk opened 3 years ago

tdanyluk commented 3 years ago

I use fakeAsync for some test cases in my daily work. I have accidentally written a test case which looks like this:

FakeAsync().run((fakeAsync) async {
  functionWithTimer();
  fakeAsync.elapse(t);
  final x =  await otherFunction();
  expect(x, expectedX);
});

I only realized a few weeks later (after the code went through a review by multiple experienced engineers and it was submitted), that the expectation doesn't actually run. I could add a throw Exception() after the expect call, and my test case would still succeed. This is because asynchronous callbacks passed to FakeAsync.run are effectively not awaited.

Unfortunately I was not the only one making this mistake, but I have seen this in multiple code files.

I'm wondering whether this kind of errors could be prevented.

Here is a naive idea:

Please feel free to contact me if you need an example for the erroneous use-case or the possible solution.

rrousselGit commented 2 years ago

An alternative solution could be https://github.com/dart-lang/test/issues/2309 I think

gnprice commented 1 year ago

A duplicate report dart-lang/test#2312 has some handy repro cases. This test correctly fails:

test('test smoke test -- this test should fail', () {
  fakeAsync((async) {
    expect(true, isFalse);
  });
});

But this one unexpectedly passes:

test('test smoke test -- this test should fail', () async {
  fakeAsync((async) async {
    expect(true, isFalse);
  });
});

Then here's a slightly more complex example, which one might expect to pass:

import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';

void main() {
  test('test smoke test -- this test should pass', () async {
    await fakeAsync((async) async {
      final future = doWork();
      async.elapse(const Duration(seconds: 2));
      final value = await future;
      expect(value, 1);
    });
  });
}

Future<int> doWork() async {
  await Future<void>.delayed(const Duration(seconds: 1));

  return 1;
}

but instead it gets stuck and times out:

$ dart test main.dart
00:30 +0 -1: test smoke test -- this test should pass [E]                                                                                   
  TimeoutException after 0:00:30.000000: Test timed out after 30 seconds. See https://pub.dev/packages/test#timeouts
  dart:isolate  _RawReceivePortImpl._handleMessage

To run this test again: /Users/bartek/fvm/versions/stable/bin/cache/dart-sdk/bin/dart test main.dart -p vm --plain-name 'test smoke test -- this test should pass'
00:30 +0 -1: Some tests failed.                                                                                                             

Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'dart test --chain-stack-traces'.