fake_async icon indicating copy to clipboard operation
fake_async copied to clipboard

Add some way to go into a "normal" execution mode, and then back into being controlled by fake async

Open natebosch opened this issue 4 years ago • 17 comments

Sometimes it's nice to be able to have a little block of async/await code in your test. await is very tricky to use in a fake async zone since it will never complete until either elapse or flushMicrotasks is called, but you can't get past it until then.

It might be possible to add some way that at least the event loop and microtask queue still operate normally. Making timers fire would likely not be possible, but also doesn't impact the await case.

natebosch avatar Aug 19 '20 18:08 natebosch

cc @mehmetf - I'm not sure if there are any testing issues or confusion you've seen that would be improved by this.

I don't have immediate plans to start working on this and I don't know how difficult it would be.

natebosch avatar Aug 19 '20 18:08 natebosch

Doesn't this invalidate the use case for fake_async? Why use it at all?

mehmetf avatar Aug 19 '20 18:08 mehmetf

It wouldn't be the general behavior, it would be something you enter/exit in your test.

// Some interaction with the production code using fake async
...
// now I have a little work I want to do in the test body.
fakeAsync.useNormalEventLoop();
await doSomething();
await doMoreStuff();
fakeAsync.useControlledEventLoop();
// More normal use of fake async

So most of the time fakeAsync behaves as it does today. In edge cases where it's convenient to have some "normal" async behavior you can enter that mode.

It would also mean that fakeAsync can be used to more eagerly fire timers that would take a long time otherwise, without interrupting anything else.

natebosch avatar Aug 19 '20 18:08 natebosch

Ah.

That's currently possible in flutter_test via runAsync.

await tester.runAsync(() {
   // Code runs in normal event loop
});

mehmetf avatar Aug 19 '20 19:08 mehmetf

Oh, I like that API too. I didn't think we would get past that outer await but it looks like it works to do something like await Zone.root.run so that should.

We could add fakeAsync.runNormally<R>(R Function()); that could be used like this.

natebosch avatar Aug 19 '20 19:08 natebosch

That would be another interesting API to investigate. In particular it would be nice if the useNormalEventLoop behavior I proposed above. There is still a potential foot-gun with the test.runAsync approach in that if you await a future that originated in the other zone it won't ever complete.

var f = Future.value(1); // Created in the FakeAsync zone.
await tester.runAsync(() {
  await f; // stuck here forever.
});

natebosch avatar Aug 19 '20 21:08 natebosch

Yep. That was the original motivation why I was looking into running setup/teardown in consistent zones.

setup(() {
  // runs in real async
  // some service gets created which contains dormant futures.
});

test(() {
  // runs in fake async
  // some widget access the service and activates the future which was created in real async.
});

This doesn't even have runAsync() call.

mehmetf avatar Aug 19 '20 21:08 mehmetf

@natebosch Any update on this?

HerrNiklasRaab avatar Nov 14 '20 00:11 HerrNiklasRaab

Any update on this?

No, this isn't something we are actively pursuing. This is something we could push on if we find more concrete use cases and have a solid idea of the design that addresses them.

natebosch avatar Nov 16 '20 18:11 natebosch

The case explained in the comment from @natebosch is very difficult to track down. Have you considered adding a list of these kind of gotchas of this lib to the readme?

jeduden avatar Sep 09 '21 14:09 jeduden

This stuff is quite confusing, is it why my test is timing out? It seems like when I start my mock server in setUp, my test times out and doesn't even seem to start running. If I create it in-line inside the group, it works fine

Future<TestServer> setup() async {
  final server = TestServer([
    MockPost("/oauth/token", responseCode: 403),
  ]);
  await server.start();
  return server;
}

void main() async {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group("Failed Login Test", () {
    late TestServer server;

    setUp(() async {
      print("\n\nsetup started\n\n");
      server = await setup();
      print("\n\nsetup done\n\n");
    });

    tearDown(() async {
      await server.stop();
    });

    testWidgets(
      "entering the wrong password and submitting should"
      " show an error banner saying Invalid Credentials",
      (tester) async {
        print("\n\ntest started\n\n");
        await app.main(["test_config.json"]);
        await tester.pumpAndSettle();

        final loginPage = LoginPageModel(tester);
        await loginPage.waitForLoginScreenRendered();
        await loginPage.enterEmail("[email protected]");
        await loginPage.enterPassword("wrong password");
        await loginPage.tapLoginButton();
        await loginPage.expectIncorrectPassword();
      },
      timeout: Timeout(Duration(minutes: 3)),
    );
  });
}

MilesAdamson avatar Nov 22 '21 18:11 MilesAdamson

How about fakeAsync.await(myFuture)? I don't think this will cover stream cases, but internally the library could pump the event loop unit that future completes, using a completer maybe.

feinstein avatar Feb 01 '22 23:02 feinstein

There is any solution to this issue?

omensight avatar Dec 08 '22 16:12 omensight

Ah.

That's currently possible in flutter_test via runAsync.

await tester.runAsync(() {
   // Code runs in normal event loop
});

Yes but runAsync isn't recommended as it can lead to flaky tests.

@natebosch I am wondering if the limitations of fake_async should be included in the docs as well.

feinstein avatar Dec 08 '22 17:12 feinstein

How about fakeAsync.await(myFuture)?

This looks promising. I wonder if this combined with a lint to avoid async for these callbacks would be sufficient.

natebosch avatar Feb 21 '23 22:02 natebosch

What's the issue with 'runAsync'?

MelbourneDeveloper avatar Dec 12 '23 19:12 MelbourneDeveloper

It leads to flaky tests, its docs explains a bit why it should be avoided if possible.

feinstein avatar Dec 12 '23 19:12 feinstein