fake_async
fake_async copied to clipboard
Add some way to go into a "normal" execution mode, and then back into being controlled by fake async
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.
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.
Doesn't this invalidate the use case for fake_async? Why use it at all?
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.
Ah.
That's currently possible in flutter_test via runAsync
.
await tester.runAsync(() {
// Code runs in normal event loop
});
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.
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.
});
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.
@natebosch Any update on this?
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.
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?
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)),
);
});
}
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.
There is any solution to this issue?
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.
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.
What's the issue with 'runAsync'?
It leads to flaky tests, its docs explains a bit why it should be avoided if possible.