test
test copied to clipboard
Zone variables are reset inside of tests
I'd like to use zone variables inside of tests but realized that that's not possible.
I couldn't find anything in the docs on why this shouldn't be possible so I wanted to ask here if that's what one should expect?
group("group", () {
runZoned(() {
final zonedVar = Zone.current["var"];
test("test", () {
expect(zonedVar, true);
});
test("test", () {
final zonedInsideTestVar = Zone.current["var"];
expect(zonedInsideTestVar, true); /// fails
});
}, zoneValues: {"var": true});
});
Thanks.
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:
void zoneTest(dynamic name, dynamic action(), {
String testOn, Timeout timeout, dynamic skip, dynamic tags,
Map<String, dynamic> onPlatform, int retry, bool solo: false }) {
test(name, Zone.current.bind(action),
testOn: testOn, timeout: timeout, skip: skip, tags: tags,
onPlatform: onPlatofrm, retry: retry, solo: solo);
}
Thank you for the response!
I've tried your suggestion (I assume you meant bindCallback) but it fails now with a
Bad state: expect() may only be called within a test.
The adjusted piece of code:
group("group", () {
runZoned(() {
final zonedVar = Zone.current["var"];
test("test a", () {
expect(zonedVar, true);
});
test("test b", Zone.current.bindCallback(() {
final zonedInsideTestVar = Zone.current["var"];
expect(zonedInsideTestVar, true); // fails
}));
}, zoneValues: {"var": true});
});
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.
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.
This is presumably what prevents me from using package:clock to do:
withClock(fakeClock, () {
test(...);
test(...);
test(...);
});
which is a bit unfortunate.
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.
Not ideal but it can serve as a workaround.
I think that this should stay open until https://github.com/dart-lang/test/issues/1023#issuecomment-487825768 is done, or at a minimum I think that this should be better documented.
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 https://github.com/dart-lang/clock/issues/17.)
So to reproduce the failed expectation from the original comment, you need to add an initial
test('', () {});
line.
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()forkZone.currentmight 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
zoneparameter totest()to allow specifying an ancestorZone?
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:
https://github.com/dart-lang/test/blob/fccb9bba6db75c211ebacc908c2029e40bbc1c3f/pkgs/test_api/lib/src/backend/invoker.dart#L161-L165
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.
https://github.com/dart-lang/test/blob/fccb9bba6db75c211ebacc908c2029e40bbc1c3f/pkgs/test_api/lib/src/remote_listener.dart#L262-L264
It's looking to me like trying to solve this by allowing a Zone which is created outside of the test framework is not feasible.
I'm going to explore https://github.com/dart-lang/test/issues/377 further as a solution here.
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!
Same here - just ran into that issue again and spent quite some time until I finally was pointed to this issue