What is the difference between tick() and flush() in angular testing?
Asked Answered
J

2

36

On the angular documentation I see these two functions, tick() and flush(). Both of these seem to do similar things. From the angular documentation, it says for tick:

Simulates the asynchronous passage of time for the timers in the fakeAsync zone.

and for flush:

Simulates the asynchronous passage of time for the timers in the fakeAsync zone by draining the macrotask queue until it is empty. The returned value is the milliseconds of time that would have been elapsed.

Can anyone explain the difference to me?

EDIT (answered in the comments):

In addition, in the angular documentation tick() is used without parameters, and the comment on that line even uses the phrase "flush"

it('should display error when TwainService fails', fakeAsync(() => {
  // tell spy to return an error observable
  getQuoteSpy.and.returnValue(
    throwError('TwainService test failure'));

  fixture.detectChanges(); // onInit()
  // sync spy errors immediately after init

  tick(); // flush the component's setTimeout()

  fixture.detectChanges(); // update errorMessage within setTimeout()

  expect(errorMessage()).toMatch(/test failure/, 'should display error');
  expect(quoteEl.textContent).toBe('...', 'should show placeholder');
}));
Janae answered 7/6, 2019 at 17:23 Comment(0)
K
41

They do different things relative to async operations that were previously started. For example; calling setTimeout(...) starts an async operation.

  • tick() moves time forward.
  • flush() moves time to the end.

This can be better illustrated with the unit tests for those functions.

Tick

This unit test shows tick being used to move time forward in steps until all 10 timers have finished. Tick is called multiple times.

https://github.com/angular/angular/blob/master/packages/core/test/fake_async_spec.ts#L205


      it('should clear periodic timers', fakeAsync(() => {
           let cycles = 0;
           const id = setInterval(() => { cycles++; }, 10);

           tick(10);
           expect(cycles).toEqual(1);

           discardPeriodicTasks();

           // Tick once to clear out the timer which already started.
           tick(10);
           expect(cycles).toEqual(2);

           tick(10);
           // Nothing should change
           expect(cycles).toEqual(2);
         }));

Flush

This unit test shows that all async tasks should be finished when it returns, and that the returned value tells you how long it took for them to finish.

https://github.com/angular/angular/blob/master/packages/core/test/fake_async_spec.ts#L273

      it('should flush multiple tasks', fakeAsync(() => {
           let ran = false;
           let ran2 = false;
           setTimeout(() => { ran = true; }, 10);
           setTimeout(() => { ran2 = true; }, 30);

           let elapsed = flush();

           expect(ran).toEqual(true);
           expect(ran2).toEqual(true);
           expect(elapsed).toEqual(30);
         }));
Keddah answered 7/6, 2019 at 18:18 Comment(6)
That makes sense, but from the angular documentation, using tick() without any parameters seems to effectively do a flush.Janae
@JustinKavalan yeah, I read that too. So basically without a parameter it means that the tick() moves all the way forward. So it is the same as a flush(). The difference is that tick() doesn't return how much time elapsed.Keddah
@JustinKavalan if this answers your question. Please accept it as the answer.Keddah
I just wanted to add that tick() and flush() are not equal. I have a test where I spy router.navigate inside a subscription triggered by a click, and using tick() leaves a periodic timer in the queue whereas flush() doesn't. (Interestingly flush() always returns 500 ms in this test.)Progeny
I thought that tick() with no arguments just runs immediate timers, like setTimeout(() => doSomething(), 0). flush() is basically while(testZone.hasPendingTasks) { tick(1); }Allow
Just to also clarify for future readers, because we have conflicting information in the comments above me: tick() without parameters IS NOT the same as flush(). Coderer is correct, tick() runs just the outstanding tasks, which means timeouts and intervals with a value > 0 are not processed by tick(). See answer from Angular dev: github.com/angular/angular/issues/43625#issuecomment-930164269Lesser
R
1

As of Angular 15, tick and flush call Zone.js's FakeAsyncTestZoneSpec.

See async.spec.ts for a complete set of example tests.

it('tick executes all microtasks and macrotasks', fakeAsync(() => {
  const spy = jasmine.createSpy('interval');
  zone.runTask(() => queueMicrotask(() => { }));
  zone.runTask(() => setInterval(spy, 1));
  zone.runTask(() => queueMicrotask(() => { }));
  expect(zone.hasPendingMicrotasks).toBeTrue();
  expect(zone.hasPendingMacrotasks).toBeTrue();

  tick(1);
  expect(zone.hasPendingMicrotasks).toBeFalse();
  expect(zone.hasPendingMacrotasks).toBeTrue();
  expect(spy).toHaveBeenCalledOnceWith();

  discardPeriodicTasks();
}));

it('flush executes all microtasks and non-periodic macrotasks', fakeAsync(() => {
  const spy = jasmine.createSpy('interval');
  zone.runTask(() => queueMicrotask(() => { }));
  zone.runTask(() => setTimeout(spy));
  zone.runTask(() => queueMicrotask(() => { }));
  expect(zone.hasPendingMicrotasks).toBeTrue();
  expect(zone.hasPendingMacrotasks).toBeTrue();

  flush();
  expect(zone.hasPendingMicrotasks).toBeFalse();
  expect(spy).toHaveBeenCalledOnceWith();
}));
Rendarender answered 31/3 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.