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

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

Open
natebosch opened this issue Aug 19, 2020 · 17 comments
Labels
package:fake_async type-enhancement A request for a change that isn't a bug

Comments

@natebosch
Copy link
Member

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 natebosch added the type-enhancement A request for a change that isn't a bug label Aug 19, 2020
@natebosch
Copy link
Member Author

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.

@mehmetf
Copy link

mehmetf commented Aug 19, 2020

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

@natebosch
Copy link
Member Author

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.

@mehmetf
Copy link

mehmetf commented Aug 19, 2020

Ah.

That's currently possible in flutter_test via runAsync.

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

@natebosch
Copy link
Member Author

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
Copy link
Member Author

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.
});

@mehmetf
Copy link

mehmetf commented Aug 19, 2020

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.

@HerrNiklasRaab
Copy link

@natebosch Any update on this?

@natebosch
Copy link
Member Author

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.

@jeduden
Copy link

jeduden commented Sep 9, 2021

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?

@MilesAdamson
Copy link

MilesAdamson commented Nov 22, 2021

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@email.com");
        await loginPage.enterPassword("wrong password");
        await loginPage.tapLoginButton();
        await loginPage.expectIncorrectPassword();
      },
      timeout: Timeout(Duration(minutes: 3)),
    );
  });
}

@feinstein
Copy link

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.

@omensight
Copy link

There is any solution to this issue?

@feinstein
Copy link

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.

@natebosch
Copy link
Member Author

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.

@MelbourneDeveloper
Copy link

What's the issue with 'runAsync'?

@feinstein
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package:fake_async type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

9 participants