Deterministic testing of a polling task in EmberJS
Tags: ember, javascript, testing
Published: 2020-01-21 17:03:50

## The problem

Let's have a service with following [ember-concurrency](http://ember-concurrency.com/) tasks. The service will do some work in an infinite loop. Let's say poll data from a backend:

```js
// services/data-fetch.js

export default Service.extend({
  startPolling: task(function* () {
    yield timeout(1000);
    this.fetchData.perform();
  }),

  fetchData: task(function* () {
    let updatedData = yield backend.fetchData;
    this.set('data', updatedData);

    this.startPolling.perform();
  }),
});
```

We can then start the polling by simply clicking on a button:


```hbs
// my-component.hbs

<button {{on "click" (perform dataFetch.startPolling)}}>
  start
</button>

<p data-test-current-value>
  Current value is: {{dataFetch.data}}
</p>
```

Let's now say our backend will give us a sequence of following values: `["preparing task", "preparing task", "processing task", "saving results", "done", "done", "done", ...]`

And our _task_ (got it?) is to check that the UI printed out at one point the string `processing task`. How do we do it? How do we `await` in tests till after certain amount of iterations or after certain amount of time?

## Note about active waiting in tests

You can use `waitUntil` from [ember-test-helpers](https://github.com/emberjs/ember-test-helpers/blob/master/API.md#waituntil):

```js
// my-component-test.js

await this.component.clickButton();
await waitUntil(() => this.component.currentValue === 'processing task');
assert.equal(this.component.currentValue, 'processing task', 'yay!');
```

The thing is: If you _can_ avoid active waiting (`yield timeout(1000)`) in tests, you really **should**. Ember tests are in general very quick and few of those waiters can significantly slow down your test suite.

Also the problem here is that `waitUntil` polls for changes in regular intervals, so if it lags for any reason, then you might **miss** your desired state and end on one of the states **after** that. And trust me that this situation happens more than you would expect (it's the reason for this article).

## Solution

The idea is to bypass the _automatic_ polling and make it more _deterministic_ by driving the requests _manually_.

```js
// my-component-test.js
module('Integration | Component | polling button', function(hooks) {
  setupRenderingTest(hooks);

  hooks.beforeEach(function() {
    // ember-cli-page-object helper
    this.component = create({
      clickButton: clickable("button"),
      currentValue: text("[data-test-current-value]")
    });

    this.dataFetch = this.owner.lookup('service:data-fetch');
    // This slightly weird syntax is needed because we're stubbing ember-concurrency task
    sinon.stub(this.dataFetch.startPolling, 'perform');
  });

  test('it can display "processing task"', async function(assert) {
    await render(hbs`{{my-component}}`);

    await this.component.clickButton();

    // Manually advance fetch-data service internal state
    await this.dataFetch.fetchData.perform();
    assert.equal(this.component.currentValue, 'preparing task', 'we are preparing task');

    await this.dataFetch.fetchData.perform();
    await this.dataFetch.fetchData.perform();
    assert.equal(this.component.currentValue, 'processing task', 'yay!');
  });
});
```