Angular Testing: Avoid done() function

Let’s talk about harmfulness of real asynchronicity in tests.

How to check the value produced from Observable, Promise, or callback?

To access variables in the callback, we have to be in its function scope!

it('should be green', () => {
anyObservable()
.subscribe((el) => {
expect(el).toBeTruthy();
});

});

When we’re handling asynchronous operation?

Think of any DOM event listeners, HTTP calls, Websockets, animations, own state management events, timers, intervals, Promises and more.

it('should be green for async operation', (done) => {
timeout(500)
.subscribe((el) => {
expect(el).toBeTruthy();
done();
});
});

Poor assumptions of done()

The example above seems to be correct, but it only works under very specific circumstances. There are some common false assumptions of what the done() function does that lead to this confusion.

  1. 😩 When Observable emits 2x, but the second time it does something different than we expect = test is green
  2. 🛑 When Observable errors after first emit = test is green
  3. ⌛️ When Observable never emits = test timeouts = slow unit test
  4. 🏁 When Observable does complete before first emit = test timeouts = slow unit test

Do we always need to use done() in callback?

When callbacks are synchronous, we don’t really need to use expect() inside callback.

it('should be green for sync', () => {
// given
const result = [];

// when
of(1, 2)
.subscribe((el) => result.push(el));

// then
expect(result).toEqual([1, 2]);
});
  1. When Observable emits 2x, but second times it does something different than we expect = test fails
  2. When Observable errors after first emit = test fails
  3. When Observable never emits = test fails
  4. When Observable does complete before first emit = test fails
We should mock async events to be sync, controlled events.

How to mock async operations? fakeAsync()

Testing asynchronous code is the more typical. Asynchronous tests can be painful. The best way to handle them? Avoid!

it('should be green for async', fakeAsync(() => {
// given
const result = [];

// when
interval(1000).subscribe((el) => result.push(el));
tick(2000);

// then
expect(result).toEqual([0, 1]);
}));

Additional advantages of using fakeAsync()

  1. We won’t forget done() when we don’t use it
  2. Test flow is clear and static— expect() always at the end, always executing
  3. We’re sure we test exactly one async behavior at the time
  4. We won’t make test utterly slow by using real async operations — think of setTimeout for 5 seconds.

JavaScript performance-solver at @Dynatrace. JavaScript trouble-maker on my own

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store