Previous: Derived State
Next: TypeScript / Glint
Ember doesn't yet have strong conventions for testing long-term timers and
polling loops, and since many of the use cases that ember-concurrency
addresses involves heavy use of
timeout()
, and often times within a (possibly infinite) loop,
it can be difficult to figure out how to test code that makes heavy use of
such things within ember-concurrency tasks.
NOTE: this is an area of active development within the Ember community, particularly amongst ember-concurrency users; in due time we will probably have more official API (possibly in the form of another addon) to help make testing time more manageable, but in the meantime, this page documents some common approaches to testing time with present-day tooling.
Consider the following (common) pattern for polling a server for changes:
The above example uses ember-concurrency tasks; to demonstrate that these issues aren't limited to ember-concurrency tasks, here is how the same logic might be written without ember-concurrency:
Both of these cases involve a "poll loop": on every iteration, do something asynchronous, then pause for some period of time, then repeat.
If, within an acceptance test, you
visit()
ed the page that causes this loop to start, your
acceptance test case would "hang" and eventually fail with a QUnit test
timeout. The reason this happens is that the Ember testing tools are aware
of all timers created via
Ember.run.later
(and ember-concurrency's
timeout()
helper internally uses
Ember.run.later
), and will wait for all timers to "settle"
before allowing the test to proceed. But if you have a timer within a
loop, the timers will never settle, and hence your test will hang.
The solution, one way or another, is to "break" the timer loop when in a testing environment. Here are all the ways to do that, each with their own problems / tradeoffs:
Ember.testing
checks in your code
This is sufficient when it's satisfactory to just test a single iteration of a loop, but a) it won't test that the task continues to loop, and b) it's unfortunate to have to riddle your actual code with testing logic.
Ember.run.cancelTimers
in your test caseThis is the approach used by the ember-concurrency documentation site tests; since any of the pages on this docs site might demonstrate a live ember-concurrency task with a timer loop, all of the acceptance tests automatically cancel all outstanding timers after 500ms to effectively stop all tasks wherever they're paused.
If you're testing code that just uses long timers, but not necessarily
loops, you might still run into the problem of test cases that take too
long to complete, or might hit the QUnit timeout. A common solution to
this problem is to use much smaller millisecond timer values in a testing
environment. You can either do this by checking
Ember.testing
wherever you set a timer, or, more elegantly, you can define common timer
values in a config file, import the timer values wherever you need to set
a timer, and in test environments, the config file specifies much smaller
values so that the timers elapse more quickly.
The above solutions leave much to be desired. Hopefully a definitive
solution that produces clear, deterministic, consistent results will
emerge from the community. There are some
ideas
floating around, and if you're interested in contributing to the
discussion please join the
#e-concurrency
channel on the
Ember Community Discord server.
Also, if you're finding success with a testing approach that wasn't mentioned here, please open a GitHub issue with your ideas or open a Pull Request to add additional docs to this page.
Sometimes it's not obvious why a Task was canceled; in these cases you can
use the
debug
Task Modifier on a specific task e.g.
@task({ debug: true }) *nameOfTask { /* ... */ }
, which will
provide some logging about the task's lifecycle, e.g.
TaskInstance 'nameOfTask' was canceled because the object it lives
on was destroyed or unrendered
.
Check the requirements to see what you need to install.
If you are sure that you fulfilled the requirements correctly, but are
still experiencing weird errors, install
ember-cli-dependency-lint
to ensure that you are not accidentally including outdated versions of
ember-concurrency
as a transitive dependency. You may need to file an issue with the
relevant dependency to update their ember-concurrency dependency range.
Alternatively, you might resort to using something like Yarn resolutions
to enforce your application's version of ember-concurrency, but this
depends on how its being used by the dependency.
If it's still not working after that, please reach out on the
#e-concurrency
channel on the
Ember
Community Discord server
or file a
new issue.
Previous: Derived State
Next: TypeScript / Glint