You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It's probably not deliberate, but you have to deliberately capture the zone of the test call in order for this to work, and that is not done.
The call to test simply registers a function and a name in the group, and not any zone. When that function is later called, it won't be in the zone you expect.
It might make sense for the test package to record the zone at the call to test, but it's a mostly useless complication in 99.9% of tests.
So, if you want to remember the zone for a function, I recommend you do that explicitly using Zone.bind:
test("test", Zone.current.bind(() {
final zonedInsideTestVar =Zone.current["var"];
expect(zonedInsideTestVar, true); /// should no long fail
}));
If you use that often, you can consider making a shortcut:
Ah, that means that the test framework does use zones internally. The way it checks that expect is used within a test is that it creates a new zone per test, and runs the test in that zone. Since the bindCallback replaces the current zone with the one the function was bound in, that throws away the test zone.
So, the problem is that the test zone is not forked from the current zone at the point where the test is created.
You can use a zone inside of test functions, but you have to do it per test.
If you do that a lot you could write a function that wraps the test function, testWithClock or similar and runs the callback inside a zone, inside a call to test.
BTW, for anyone trying this, the actual behavior (at least as of package:test 1.14.3) seems to be that first invocation of test() does fire its callback in (a descendant of) the caller's Zone, but subsequent invocations of test() continue firing callbacks in (descendants of) that first Zone instead of in the callers' Zones. (I think that this explains the weirdness I observed in dart-lang/tools#699.)
So to reproduce the failed expectation from the original comment, you need to add an initial
I don't know if there's any concern that making each call to test() fork Zone.current might break existing code, but if there is, how about adding an optional named zone parameter to test() to allow specifying an ancestor Zone?
I don't know if there's any concern that making each call to test() fork Zone.current might break existing code
I don't think anyone has looked at it closely. We could give it a try and see if it surfaces any problems.
how about adding an optional named zone parameter to test() to allow specifying an ancestor Zone?
I worry that ands up being even harder to understand than to say "do zone stuff within a test callback". This likely isn't something we'd drive on until after the null safety migration so it's not something we'd accept a PR for in the short term.
I started to look at this and have found a few things that make this challenging.
We currently have multiple levels of zones surrounding the test body. The most interesting ones to look at now are in Invoker, and Declarer.
We rely on the user test code running in the zone we define for things like quality of stack trace improvements (the optional Chain.capture) as well as keeping track of the current declarer and invoker by setting them in zone variables.
So if we were going to run the user provided callback in a zone they provide, we'd have to surround all of these layers of zone with the user's zone. However that causes a problem highlighted by this comment:
/// The zone that the top level of [_test.body] is running in.
///
/// Tracking this ensures that [_timeoutTimer] isn't created in a
/// timer-mocking zone created by the test.
Zone _invokerZone;
We need to make sure we have a timer that can run in a zone which is not impacted by the user's use of FakeAsync. The easier user of FakeAsync zones is specifically the goal that @mehmetf is working towards.
I'll explore a little bit whether that _invokerZone can be handled differently, however I might hit issues outside of that. There are other uses of zones even outside this specific behavior that also rely on setting zone variables like the RemoteListener which makes it risky for test cases to suddenly start running in a completely different zone than they used to.
I just wanted to bump this as we have run into this issue multiple times. This behavior is quite confusing so it would be nice if we could at least document it in the meantime. Thanks!
Activity
lrhn commentedon Apr 29, 2019
It's probably not deliberate, but you have to deliberately capture the zone of the
testcall in order for this to work, and that is not done.The call to
testsimply registers a function and a name in the group, and not any zone. When that function is later called, it won't be in the zone you expect.It might make sense for the
testpackage to record the zone at the call totest, but it's a mostly useless complication in 99.9% of tests.So, if you want to remember the zone for a function, I recommend you do that explicitly using
Zone.bind:If you use that often, you can consider making a shortcut:
modulovalue commentedon Apr 29, 2019
Thank you for the response!
I've tried your suggestion (I assume you meant
bindCallback) but it fails now with aBad state: expect() may only be called within a test.The adjusted piece of code:
Yes, this may be a niche issue and if it's not fixable without 'useless complication' I'd be fine with leaving it at that.
Thanks.
lrhn commentedon Apr 30, 2019
Ah, that means that the test framework does use zones internally. The way it checks that
expectis used within a test is that it creates a new zone per test, and runs the test in that zone. Since thebindCallbackreplaces the current zone with the one the function was bound in, that throws away the test zone.So, the problem is that the test zone is not forked from the current zone at the point where the test is created.
jamesderlin commentedon May 15, 2020
This is presumably what prevents me from using
package:clockto do:which is a bit unfortunate.
jakemac53 commentedon May 15, 2020
You can use a zone inside of
testfunctions, but you have to do it per test.If you do that a lot you could write a function that wraps the
testfunction,testWithClockor similar and runs the callback inside a zone, inside a call totest.Not ideal but it can serve as a workaround.
jamesderlin commentedon May 18, 2020
I think that this should stay open until #1023 (comment) is done, or at a minimum I think that this should be better documented.
jamesderlin commentedon May 19, 2020
BTW, for anyone trying this, the actual behavior (at least as of
package:test1.14.3) seems to be that first invocation oftest()does fire its callback in (a descendant of) the caller'sZone, but subsequent invocations oftest()continue firing callbacks in (descendants of) that firstZoneinstead of in the callers'Zones. (I think that this explains the weirdness I observed in dart-lang/tools#699.)So to reproduce the failed expectation from the original comment, you need to add an initial
line.
jamesderlin commentedon May 19, 2020
I don't know if there's any concern that making each call to
test()forkZone.currentmight break existing code, but if there is, how about adding an optional namedzoneparameter totest()to allow specifying an ancestorZone?natebosch commentedon May 19, 2020
I don't think anyone has looked at it closely. We could give it a try and see if it surfaces any problems.
I worry that ands up being even harder to understand than to say "do zone stuff within a test callback". This likely isn't something we'd drive on until after the null safety migration so it's not something we'd accept a PR for in the short term.
natebosch commentedon Jun 17, 2020
I started to look at this and have found a few things that make this challenging.
We currently have multiple levels of zones surrounding the test body. The most interesting ones to look at now are in
Invoker, andDeclarer.We rely on the user test code running in the zone we define for things like quality of stack trace improvements (the optional
Chain.capture) as well as keeping track of the current declarer and invoker by setting them in zone variables.So if we were going to run the user provided callback in a zone they provide, we'd have to surround all of these layers of zone with the user's zone. However that causes a problem highlighted by this comment:
test/pkgs/test_api/lib/src/backend/invoker.dart
Lines 161 to 165 in fccb9bb
We need to make sure we have a timer that can run in a zone which is not impacted by the user's use of
FakeAsync. The easier user ofFakeAsynczones is specifically the goal that @mehmetf is working towards.I'll explore a little bit whether that
_invokerZonecan be handled differently, however I might hit issues outside of that. There are other uses of zones even outside this specific behavior that also rely on setting zone variables like theRemoteListenerwhich makes it risky for test cases to suddenly start running in a completely different zone than they used to.test/pkgs/test_api/lib/src/remote_listener.dart
Lines 262 to 264 in fccb9bb
natebosch commentedon Jul 1, 2020
It's looking to me like trying to solve this by allowing a
Zonewhich is created outside of the test framework is not feasible.I'm going to explore #377 further as a solution here.
alanrussian commentedon Feb 12, 2021
I just wanted to bump this as we have run into this issue multiple times. This behavior is quite confusing so it would be nice if we could at least document it in the meantime. Thanks!
dkbast commentedon Dec 7, 2022
Same here - just ran into that issue again and spent quite some time until I finally was pointed to this issue