Jasmine
asynchronous testing
done function
JavaScript testing
Jasmine 2.0

Test asynchronous functionality in Jasmine 2.0.0 with done

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Asynchronous behavior is where many test suites become flaky or misleading. In Jasmine 2.0.0, the done callback is the main way to tell the runner, "this spec is not finished yet, keep waiting until I say it is."

Why done Exists

A normal Jasmine spec finishes as soon as the function body returns. That is fine for synchronous code, but it breaks when the code under test uses timers, callbacks, or network-style APIs. Without extra coordination, Jasmine would evaluate expectations too early or mark the test complete before the asynchronous work even runs.

Passing done into the it callback changes that contract. Jasmine waits until done() is called before deciding that the spec passed.

Basic Callback Example

The example below tests a function that calls its callback after a short delay.

javascript
1function fetchMessage(callback) {
2  setTimeout(function () {
3    callback("ready");
4  }, 20);
5}
6
7describe("fetchMessage", function () {
8  it("returns the async value", function (done) {
9    fetchMessage(function (value) {
10      expect(value).toBe("ready");
11      done();
12    });
13  });
14});

The important part is the order of operations. The expectation runs inside the callback, and done() is called only after the assertion succeeds. If you called done() before the callback executed, Jasmine would finish the test too soon.

Testing Error Paths with done.fail

When asynchronous code throws or rejects later, a plain try block around the whole spec is not enough. The error happens on another turn of the event loop. Jasmine provides done.fail so you can fail the spec explicitly when the async path reports a problem.

javascript
1function loadUser(callback, onError) {
2  setTimeout(function () {
3    onError(new Error("network issue"));
4  }, 20);
5}
6
7describe("loadUser", function () {
8  it("reports asynchronous failures", function (done) {
9    loadUser(
10      function () {
11        done.fail("success callback should not run");
12      },
13      function (error) {
14        expect(error.message).toBe("network issue");
15        done();
16      }
17    );
18  });
19});

This pattern is more reliable than letting an error disappear into the console while the test times out.

Coordinating Spies and Timers

A common use case is verifying that an async dependency was called with the expected data. Spies work well here, as long as you keep the expectation inside the async completion path.

javascript
1function saveLater(api, record) {
2  setTimeout(function () {
3    api.save(record);
4  }, 10);
5}
6
7describe("saveLater", function () {
8  it("calls api.save after the delay", function (done) {
9    var api = {
10      save: jasmine.createSpy("save")
11    };
12
13    saveLater(api, { id: 7 });
14
15    setTimeout(function () {
16      expect(api.save).toHaveBeenCalledWith({ id: 7 });
17      done();
18    }, 30);
19  });
20});

You can also use Jasmine clock helpers in some cases, but done remains the simplest mental model when real callbacks are involved.

Timeout Behavior and Test Design

If done() is never called, Jasmine treats the spec as hung and eventually fails it on timeout. That is helpful because it exposes missing callbacks, unresolved promises, or branches that did not execute. Still, a timeout failure is usually a symptom, not a diagnosis. Keep asynchronous specs narrow so you can quickly identify whether the problem is timing, setup, or incorrect expectations.

In newer Jasmine codebases you may also see promise-based or async style tests. When maintaining Jasmine 2.0.0 specifically, done is the mechanism you should expect to use most often.

Common Pitfalls

  • Forgetting to accept the done parameter causes Jasmine to treat the spec as synchronous.
  • Calling done() before the assertion runs can create false positives.
  • Not handling the error path leads to timeouts instead of meaningful failures.
  • Triggering multiple async branches and calling done() more than once can produce confusing behavior.
  • Writing broad integration tests with many timers makes asynchronous failures harder to debug.

Summary

  • 'done tells Jasmine to wait for asynchronous work before finishing the spec.'
  • Place expectations inside the callback or completion handler that proves the work finished.
  • Use done.fail for async error paths so failures are immediate and explicit.
  • Timeouts usually mean a branch never completed or done() was never reached.
  • Keep async specs focused to make failures easier to diagnose.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.