Skip to content
Jasmine — Complete JavaScript Testing Framework Reference Guide

Jasmine — Complete JavaScript Testing Framework Reference Guide

DodaTech Updated Jun 6, 2026 8 min read

Learning Path

    flowchart LR
    A["Jasminejs Overview"] --> B["Core Concepts"]
    B --> C["Intermediate Topics"]
    C --> D["Advanced Topics"]
    D --> E["Practical Applications"]
    A --> F["You Are Here"]
    style F fill:#f90,color:#fff
  

Jasmine is a behavior-driven development (BDD) testing framework for JavaScript that doesn’t require a DOM or any other framework — making it ideal for testing both Node.js and browser-based code with clean, readable syntax.

What You’ll Learn

By the end of this tutorial, you’ll write test suites with describe and it blocks, use built-in and custom matchers, spy on functions to verify calls and stub return values, mock timers with the Jasmine clock, test asynchronous code with promises and callbacks, and integrate Jasmine into your build pipeline.

Why Jasmine Matters

Testing isn’t optional — it’s how professional teams ship reliable code. Jasmine’s BDD syntax makes tests readable by non-developers (product managers, QA), bridging the communication gap between technical and non-technical team members. Jasmine is the default test runner for Angular, but works with any JavaScript framework. DodaTech uses Jasmine for unit testing Doda Browser’s extension APIs and DodaZIP’s core file-processing logic.

Security note: Understanding Reference helps build more secure applications — a core principle at DodaTech, where tools like Durga Antivirus Pro and Doda Browser rely on solid implementation practices.

Prerequisites: Solid JavaScript fundamentals. Understanding of DevOps (unit tests, assertions, mocks) is helpful.

Basic Structure — describe/it

Every Jasmine test is organized into suites (describe) and specs (it):

describe("Calculator", () => {
  let calc;

  // Runs before each `it` block — fresh setup every time
  beforeEach(() => {
    calc = new Calculator();
  });

  it("should add two numbers", () => {
    expect(calc.add(2, 3)).toEqual(5);
  });

  it("should subtract numbers", () => {
    expect(calc.subtract(5, 3)).toEqual(2);
  });

  // Runs after each `it` block — cleanup
  afterEach(() => {
    // release resources, reset state
  });
});

Line-by-line explanation:

  • describe("Calculator", () => { ... }) — defines a test suite. The first argument is a name (what are we testing?), the second is a function containing tests.
  • beforeEach(() => { ... }) — runs before every single test. Creates a fresh Calculator instance so tests don’t share state.
  • it("should add two numbers", () => { ... }) — an individual test. The string describes what the test verifies. Read it aloud: “it should add two numbers.”
  • expect(calc.add(2, 3)).toEqual(5) — an assertion. expect() returns an expectation object. .toEqual(5) is a matcher that checks deep equality.
  • afterEach(() => { ... }) — runs after every test for cleanup (closing connections, clearing timers).

Why BDD syntax matters: The test reads like a sentence: “Calculator should add two numbers.” Non-technical stakeholders can read these and understand what the system does.

Matchers — Built-in Assertions

expect(true).toBe(true);           // Identity (===) — strict equality
expect(obj).toEqual({ a: 1 });     // Deep equality — checks all properties
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect(1).toBeDefined();
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect([1, 2, 3]).toContain(2);    // Array/string inclusion
expect("hello").toMatch(/^hel/);   // Regex match
expect(5).toBeGreaterThan(3);      // Numeric comparison
expect(5).toBeLessThanOrEqual(10);
expect(fn).toThrow();              // Function must throw an error
expect(fn).toThrowError("specific error");  // Error with specific message

Negation: Add .not before any matcher:

expect(1).not.toBe(2);
expect([1, 2, 3]).not.toContain(4);

Custom Matchers

Create reusable assertions for domain-specific checks:

beforeEach(() => {
  jasmine.addMatchers({
    toBeWithinRange: function(util, customEqualityTesters) {
      return {
        compare: function(actual, min, max) {
          const result = { pass: actual >= min && actual <= max };
          if (!result.pass) {
            result.message = `Expected ${actual} to be between ${min} and ${max}`;
          }
          return result;
        }
      };
    }
  });
});

// Usage
expect(42).toBeWithinRange(40, 50);

Spies — Tracking and Stubbing

Spies replace functions with test doubles that record calls and control behavior:

describe("Spies", () => {
  it("should track calls", () => {
    const obj = { method: () => {} };
    spyOn(obj, "method");

    obj.method(1, 2, 3);

    expect(obj.method).toHaveBeenCalled();
    expect(obj.method).toHaveBeenCalledWith(1, 2, 3);
    expect(obj.method.calls.count()).toBe(1);
  });

  it("should stub a return value", () => {
    spyOn(service, "getData").and.returnValue(
      Promise.resolve([1, 2, 3])
    );
    // service.getData() now returns the promise without calling the real method

    // Other options:
    // spy.and.callThrough()  — actually call original function
    // spy.and.callFake(fn)   — call a fake implementation
    // spy.and.throwError()   — throw an error
  });

  it("should create a spy object", () => {
    const spy = jasmine.createSpyObj("service", ["get", "post"]);
    spy.get.and.returnValue("mock data");
    expect(spy.get()).toBe("mock data");
  });
});

Line-by-line:

  • spyOn(obj, "method") — replaces obj.method with a spy. The original function is NOT called.
  • and.returnValue(value) — the spy returns a specific value when called
  • and.callThrough() — calls the original function but still tracks calls
  • jasmine.createSpyObj("service", ["get", "post"]) — creates a complete mock object with spy methods

Async Testing

describe("Async", () => {
  // Modern approach: async/await
  it("should handle promises", async () => {
    const result = await asyncFunction();
    expect(result).toBe("done");
  });

  // Callback approach: done parameter
  it("should use done callback", (done) => {
    setTimeout(() => {
      expect(true).toBe(true);
      done();  // Tell Jasmine this test is complete
    }, 1000);
  });

  // Async with spies
  it("should handle promise rejection", async () => {
    spyOn(service, "fetchData").and.rejectWith("Network error");
    await expectAsync(service.fetchData()).toBeRejectedWith("Network error");
  });
});

Clock Mocking

Control time-based code without waiting:

beforeEach(() => {
  jasmine.clock().install();
});

afterEach(() => {
  jasmine.clock().uninstall();
});

it("should debounce", () => {
  const callback = jasmine.createSpy("callback");
  debounced(callback, 300);

  jasmine.clock().tick(300);  // fast-forward 300ms
  expect(callback).toHaveBeenCalled();
});

Common Mistakes

1. Sharing state between tests

If a test modifies shared state (like an array or object in a describe scope), other tests see the modified version. Always initialize fresh data in beforeEach.

2. Forgetting done() in async callback tests

Without calling done(), Jasmine considers the test passed immediately, not waiting for the async operation. The test always passes — even if the callback fails.

3. Using toBe when you need toEqual

toBe checks reference identity (===). toEqual checks deep equality. expect({a: 1}).toBe({a: 1}) fails (different objects). Use toEqual for object comparison.

4. Not cleaning up after clock mocking

If you install jasmine.clock() in beforeEach but forget uninstall() in afterEach, the clock stays mocked and all subsequent tests have broken timing.

5. Testing implementation details instead of behavior

Test what the code DOES, not how it does it. Testing internal method calls or private state makes tests brittle — they break when you refactor.

Practice Questions

1. What is the difference between toBe and toEqual?

Answer: toBe uses strict equality (===) for primitives and reference identity for objects. toEqual performs deep equality comparison on objects and arrays.

2. How do you test an asynchronous function with Jasmine?

Answer: Use async/await (modern) — await asyncFunction(); expect(result)... — or the done callback (legacy) — call done() when the async operation completes.

3. What is a spy and when would you use one?

Answer: A spy replaces a function with a test double that records calls, arguments, and can stub return values. Use spies to isolate the code under test from its dependencies.

4. How does jasmine.clock() help with testing?

Answer: It allows you to control time programmatically — fast-forwarding through delays, intervals, and timeouts without actually waiting.

Challenge

Write a test suite for a shopping cart module: test adding items, removing items, calculating totals, applying discount codes (with a spy on the discount validation API), and handling empty cart edge cases.

FAQ

What is Jasmine?
Jasmine is a behavior-driven development (BDD) testing framework for JavaScript that provides a clean, readable syntax for writing unit tests without requiring a DOM or other dependencies.
How is Jasmine different from Jest or Mocha?
Jasmine is batteries-included (built-in assertions, spies, clock). Jest adds snapshot testing and mocking on top. Mocha is more flexible but requires additional libraries (Chai, Sinon).
Does Jasmine work with Node.js?
Yes. Jasmine runs in both Node.js and browser environments. Install jasmine via npm and configure with jasmine init.
What is a test suite in Jasmine?
A test suite is defined by describe() — a group of related tests. Each test is defined by it(). Suites can be nested.
What is jasmine.createSpyObj?
It creates an object where all specified methods are spies — useful for creating mock service objects with multiple methods.
How do I run Jasmine tests?
npx jasmine (Node.js), or include jasmine.js and jasmine-html.js in a browser page, or via Karma test runner for browser tests.

Try It Yourself

# 1. Install Jasmine globally or locally
npm install --save-dev jasmine

# 2. Initialize Jasmine
npx jasmine init

# 3. Create a module to test
cat > src/calculator.js << 'EOF'
class Calculator {
  add(a, b) { return a + b; }
  subtract(a, b) { return a - b; }
  multiply(a, b) { return a * b; }
  divide(a, b) {
    if (b === 0) throw new Error("Cannot divide by zero");
    return a / b;
  }
}
module.exports = Calculator;
EOF

# 4. Create a test file
cat > spec/calculator-spec.js << 'EOF'
const Calculator = require("../src/calculator");

describe("Calculator", () => {
  let calc;

  beforeEach(() => {
    calc = new Calculator();
  });

  it("should add two numbers", () => {
    expect(calc.add(2, 3)).toEqual(5);
  });

  it("should throw on division by zero", () => {
    expect(() => calc.divide(1, 0)).toThrowError("Cannot divide by zero");
  });
});
EOF

# 5. Run tests
npx jasmine

What’s Next

Explore more software quality tools:

TopicDescription
https://tutorials.dodatech.com/tools/lodash/JavaScript utility library
https://tutorials.dodatech.com/tools/rxjs/Reactive programming

Related topics to explore:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro