← Back to DevBytes

Jasmine: Complete Testing Guide for Developers

Introduction to Jasmine

Jasmine is an open-source, behavior-driven development (BDD) testing framework for JavaScript. It does not depend on any browser, DOM, or framework — it runs on every JavaScript platform, including Node.js, browsers, and mobile environments. Jasmine was created to be simple and readable, with a clean syntax that reads like natural language, making tests easy to understand even for non-developers.

At its core, Jasmine provides a rich set of built-in matchers, a robust spying mechanism for mocking dependencies, and straightforward support for testing asynchronous code. It requires zero external dependencies and ships with everything you need to start writing tests immediately after installation.

Why Jasmine Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Testing is a critical discipline in modern software development. Without tests, codebases become fragile, refactoring becomes dangerous, and regressions slip into production unnoticed. Jasmine matters because it lowers the barrier to entry for writing tests in JavaScript:

Getting Started: Installation and Setup

Jasmine can be installed in several ways depending on your project type. The most common approaches are via npm for Node.js projects, or by downloading the standalone distribution for browser-based testing.

Installing Jasmine via npm (Node.js)

For Node.js projects, install Jasmine as a development dependency:

npm install --save-dev jasmine

Then initialize Jasmine in your project directory. This creates a spec directory and a default configuration file:

npx jasmine init

This generates the following structure:

spec/
└── support/
    └── jasmine.json   # Configuration file

You can also install Jasmine globally for convenience:

npm install -g jasmine

Installing Jasmine for Browsers

For browser-based testing, download the standalone distribution from the Jasmine GitHub releases page. It includes a lib folder with the Jasmine library, a spec folder for your tests, and a SpecRunner.html file that executes them in the browser. Simply open SpecRunner.html in any browser to run your tests.

Running Tests

To execute tests in a Node.js project, run:

npx jasmine

You can also target specific spec files:

npx jasmine spec/mySpec.js

For watch mode during development, you might pair Jasmine with a file watcher or use the --watch flag if your runner supports it. Alternatively, integrate Jasmine into your build pipeline with Karma (for browsers) or as part of a gulp/Grunt task.

Core Concepts: Suites, Specs, and Expectations

Jasmine organizes tests using three fundamental building blocks:

Here is a simple example that demonstrates all three concepts:

// A suite that groups related tests
describe('Calculator', () => {
  let calculator;

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

  // A spec for the add method
  it('should add two numbers correctly', () => {
    const result = calculator.add(2, 3);
    // An expectation with a matcher
    expect(result).toBe(5);
  });

  // Another spec for the subtract method
  it('should subtract two numbers correctly', () => {
    const result = calculator.subtract(10, 4);
    expect(result).toBe(6);
  });

  // A spec that tests edge cases
  it('should handle negative numbers in addition', () => {
    const result = calculator.add(-5, 3);
    expect(result).toBe(-2);
  });
});

The output from running these tests reads like a specification document. Jasmine reports each describe block as a heading and each it block as a passing or failing test case.

Jasmine Matchers in Depth

Jasmine ships with a comprehensive set of built-in matchers. Each matcher compares the actual value (passed to expect) against an expected value or condition. Here is a detailed reference of every built-in matcher:

Equality and Identity Matchers

describe('Equality matchers', () => {
  it('toBe - checks strict equality (===)', () => {
    const a = 42;
    const b = 42;
    expect(a).toBe(b);           // passes
    // toBe uses Object.is internally, so it's stricter than === in some edge cases
  });

  it('toEqual - checks deep equality', () => {
    const obj1 = { name: 'Alice', age: 30 };
    const obj2 = { name: 'Alice', age: 30 };
    expect(obj1).toEqual(obj2);  // passes - recursively compares properties
    expect([1, 2, 3]).toEqual([1, 2, 3]); // passes
  });

  it('toBe uses Object.is for NaN handling', () => {
    expect(NaN).toBe(NaN);       // passes - toBe handles NaN correctly
  });
});

Truthiness Matchers

describe('Truthiness matchers', () => {
  it('toBeTruthy - passes if value is truthy', () => {
    expect(true).toBeTruthy();
    expect(1).toBeTruthy();
    expect('hello').toBeTruthy();
    expect({}).toBeTruthy();
  });

  it('toBeFalsy - passes if value is falsy', () => {
    expect(false).toBeFalsy();
    expect(0).toBeFalsy();
    expect('').toBeFalsy();
    expect(null).toBeFalsy();
    expect(undefined).toBeFalsy();
  });

  it('toBeNull - specifically checks for null', () => {
    const value = null;
    expect(value).toBeNull();    // passes
    expect(undefined).not.toBeNull(); // passes
  });

  it('toBeUndefined - specifically checks for undefined', () => {
    let x;
    expect(x).toBeUndefined();   // passes
    expect(null).not.toBeUndefined(); // passes
  });

  it('toBeDefined - opposite of toBeUndefined', () => {
    const x = 'I exist';
    expect(x).toBeDefined();     // passes
  });
});

Numeric and Range Matchers

describe('Numeric matchers', () => {
  it('toBeGreaterThan, toBeLessThan, toBeGreaterThanOrEqual, toBeLessThanOrEqual', () => {
    expect(10).toBeGreaterThan(5);
    expect(3).toBeLessThan(10);
    expect(7).toBeGreaterThanOrEqual(7);
    expect(4).toBeLessThanOrEqual(4);
    expect(4).toBeLessThanOrEqual(5);
  });

  it('toBeCloseTo - for floating point comparisons', () => {
    // Avoids floating point precision issues
    expect(0.1 + 0.2).toBeCloseTo(0.3, 5);  // second argument is precision (decimal places)
    expect(3.14159).toBeCloseTo(3.14, 2);   // passes
  });

  it('toThrow - expects a function to throw an error', () => {
    const throwError = () => { throw new Error('Oops!'); };
    expect(throwError).toThrow();
    expect(throwError).toThrow('Oops!');
    expect(throwError).toThrow(/Oops/);     // regex match
    expect(() => 1 + 1).not.toThrow();      // non-throwing function
  });
});

String and Collection Matchers

describe('String and collection matchers', () => {
  it('toContain - checks if an array or string contains a value', () => {
    expect(['apple', 'banana', 'cherry']).toContain('banana');
    expect('hello world').toContain('world');
    expect([1, 2, 3]).not.toContain(42);
  });

  it('toMatch - matches against a regular expression', () => {
    expect('hello@example.com').toMatch(/^[a-z]+@[a-z]+\.[a-z]+$/);
    expect('123-456-7890').toMatch(/^\d{3}-\d{3}-\d{4}$/);
  });
});

Negation with .not

Every matcher can be negated by chaining .not before the matcher call:

describe('Negation', () => {
  it('demonstrates .not with various matchers', () => {
    expect(42).not.toBe(100);
    expect({}).not.toEqual({ name: 'Bob' });
    expect('clean').not.toContain('dirt');
    expect(true).not.toBeFalsy();
    expect(null).not.toBeTruthy();
  });
});

The toHaveBeenCalled Matchers (for Spies)

These matchers work exclusively with Jasmine spies, which we cover in detail later:

// Quick preview - detailed spy coverage below
describe('Spy matchers preview', () => {
  it('toHaveBeenCalled - verifies a spy was called', () => {
    const spy = jasmine.createSpy('mySpy');
    spy();
    expect(spy).toHaveBeenCalled();
  });

  it('toHaveBeenCalledWith - verifies specific arguments', () => {
    const spy = jasmine.createSpy('greeter');
    spy('hello', 'world');
    expect(spy).toHaveBeenCalledWith('hello', 'world');
  });

  it('toHaveBeenCalledTimes - verifies call count', () => {
    const spy = jasmine.createSpy('counter');
    spy();
    spy();
    expect(spy).toHaveBeenCalledTimes(2);
  });
});

Setup and Teardown with beforeEach, afterEach, beforeAll, afterAll

Jasmine provides lifecycle hooks to manage test state, reduce duplication, and ensure clean environments between specs. These hooks run at different granularities and are essential for writing maintainable test suites.

beforeEach and afterEach (per-spec hooks)

beforeEach runs before every single spec (it block) within the containing describe suite. afterEach runs after every spec. Use these to set up and tear down test fixtures:

describe('UserService', () => {
  let userService;
  let mockDatabase;

  // Runs before EACH spec in this suite
  beforeEach(() => {
    mockDatabase = {
      users: [],
      save: jasmine.createSpy('save').and.callFake((user) => {
        mockDatabase.users.push(user);
        return Promise.resolve(user);
      }),
      clear: jasmine.createSpy('clear').and.callFake(() => {
        mockDatabase.users = [];
      })
    };
    userService = new UserService(mockDatabase);
  });

  // Runs after EACH spec - cleans up
  afterEach(() => {
    mockDatabase.clear();
  });

  it('should create a new user', async () => {
    const user = await userService.createUser({ name: 'Alice' });
    expect(user.name).toBe('Alice');
    expect(mockDatabase.save).toHaveBeenCalled();
    expect(mockDatabase.users.length).toBe(1);
  });

  it('should start with an empty database', () => {
    // Thanks to afterEach, the database is reset before each spec
    expect(mockDatabase.users.length).toBe(0);
  });
});

beforeAll and afterAll (suite-level hooks)

beforeAll runs once before all specs in the suite. afterAll runs once after all specs have completed. Use these for expensive setup operations like database connections or server startup:

describe('Database integration', () => {
  let dbConnection;

  // Runs ONCE before all specs - expensive setup
  beforeAll(async () => {
    dbConnection = await Database.connect({
      host: 'localhost',
      port: 5432,
      database: 'test_db'
    });
    await dbConnection.migrate();
  });

  // Runs ONCE after all specs - tear down
  afterAll(async () => {
    await dbConnection.disconnect();
  });

  it('should fetch users from the database', async () => {
    const users = await dbConnection.query('SELECT * FROM users');
    expect(users).toBeDefined();
  });

  it('should insert a record', async () => {
    const result = await dbConnection.query(
      'INSERT INTO users (name) VALUES ($1)',
      ['Charlie']
    );
    expect(result.rowCount).toBe(1);
  });
});

Nested describe Blocks and Hook Scoping

Hooks in outer describe blocks run for all specs in nested suites. This allows hierarchical setup with shared base configuration and specialized overrides:

describe('ShoppingCart', () => {
  let cart;

  // Outer beforeEach runs for ALL specs in this suite and nested suites
  beforeEach(() => {
    cart = new ShoppingCart();
    cart.addItem({ name: 'base-item', price: 10 });
  });

  it('should have the base item', () => {
    expect(cart.items.length).toBe(1);
  });

  describe('when applying discounts', () => {
    let discountService;

    // Inner beforeEach runs AFTER outer beforeEach
    // So cart already has the base item at this point
    beforeEach(() => {
      discountService = new DiscountService();
      discountService.applyTo(cart);
    });

    it('should apply a percentage discount', () => {
      expect(cart.total).toBeLessThan(10);
    });

    it('should maintain at least one item', () => {
      expect(cart.items.length).toBeGreaterThanOrEqual(1);
    });
  });

  describe('when checking out', () => {
    beforeEach(() => {
      cart.checkout();
    });

    it('should mark cart as checked out', () => {
      expect(cart.isCheckedOut).toBeTruthy();
    });
  });
});

Jasmine Spies: Mocking and Stubbing

Spies are one of Jasmine's most powerful features. They allow you to replace functions with test doubles that track calls, arguments, and return values. Spies are indispensable for isolating units of code from their dependencies.

Creating a Spy with jasmine.createSpy

describe('Basic spy creation', () => {
  it('creates a bare spy function', () => {
    const mySpy = jasmine.createSpy('logger');

    // The spy is a function you can call
    mySpy('first call');
    mySpy('second call', { extra: 'data' });

    expect(mySpy).toHaveBeenCalled();
    expect(mySpy).toHaveBeenCalledTimes(2);
    expect(mySpy).toHaveBeenCalledWith('first call');
  });
});

Spying on Existing Methods with spyOn

spyOn replaces an existing method on an object with a spy, allowing you to track calls while optionally preserving or replacing the original behavior:

describe('spyOn - spying on existing methods', () => {
  let calculator;

  beforeEach(() => {
    calculator = {
      add: (a, b) => a + b,
      multiply: (a, b) => a * b,
      log: (message) => console.log(message)
    };
  });

  it('tracks calls to an existing method without changing behavior', () => {
    spyOn(calculator, 'add').and.callThrough();

    const result = calculator.add(2, 3);

    expect(calculator.add).toHaveBeenCalledWith(2, 3);
    expect(result).toBe(5); // Original behavior preserved
  });

  it('replaces the method with a stub return value', () => {
    spyOn(calculator, 'add').and.returnValue(999);

    const result = calculator.add(2, 3);

    expect(result).toBe(999);
    expect(calculator.add).toHaveBeenCalled();
  });

  it('replaces the method with a fake implementation', () => {
    spyOn(calculator, 'multiply').and.callFake((a, b) => {
      // Custom fake logic
      return (a * b) + 100;
    });

    const result = calculator.multiply(5, 4);
    expect(result).toBe(120); // (5 * 4) + 100
  });

  it('throws an error when the method is called', () => {
    spyOn(calculator, 'add').and.throwError('Calculation error');

    expect(() => calculator.add(2, 3)).toThrow('Calculation error');
  });
});

Spy Strategies in Detail

describe('Spy strategy combinations', () => {
  let api;

  beforeEach(() => {
    api = {
      fetchData: () => Promise.resolve({ data: 'real' }),
      processData: (data) => data,
      sendNotification: () => {}
    };
  });

  it('demonstrates callThrough - spy tracks but delegates to original', () => {
    spyOn(api, 'processData').and.callThrough();

    const result = api.processData('input');
    expect(result).toBe('input'); // Real method executed
    expect(api.processData).toHaveBeenCalledWith('input');
  });

  it('demonstrates returnValue - replaces return entirely', () => {
    spyOn(api, 'fetchData').and.returnValue(Promise.resolve({ data: 'mocked' }));

    api.fetchData().then(result => {
      expect(result).toEqual({ data: 'mocked' });
    });
    expect(api.fetchData).toHaveBeenCalled();
  });

  it('demonstrates callFake - provides a complete replacement function', () => {
    let sideEffectCounter = 0;

    spyOn(api, 'sendNotification').and.callFake((message) => {
      sideEffectCounter++;
      return `FAKE: ${message}`;
    });

    const result = api.sendNotification('Hello');
    expect(result).toBe('FAKE: Hello');
    expect(sideEffectCounter).toBe(1);
  });

  it('demonstrates stub - simply prevents the original from being called', () => {
    // .and.stub() is equivalent to replacing with a no-op that returns undefined
    spyOn(api, 'sendNotification').and.stub();

    const result = api.sendNotification('ignored');
    expect(result).toBeUndefined();
    expect(api.sendNotification).toHaveBeenCalled();
  });
});

Verifying Calls with Spy Matchers

describe('Spy verification matchers', () => {
  let service;

  beforeEach(() => {
    service = {
      handle: jasmine.createSpy('handle')
    };
  });

  it('toHaveBeenCalled - verifies any call occurred', () => {
    service.handle();
    expect(service.handle).toHaveBeenCalled();
    // Equivalent to:
    expect(service.handle.calls.any()).toBe(true);
  });

  it('toHaveBeenCalledWith - verifies exact argument matches', () => {
    service.handle('urgent', { priority: 1 });
    expect(service.handle).toHaveBeenCalledWith('urgent', { priority: 1 });
  });

  it('toHaveBeenCalledWith - using jasmine.anything and jasmine.any', () => {
    service.handle('data', { timestamp: Date.now() });

    // jasmine.anything() matches any non-null/undefined value
    expect(service.handle).toHaveBeenCalledWith(
      jasmine.anything(),
      jasmine.anything()
    );

    // jasmine.any(Constructor) matches any instance of a type
    expect(service.handle).toHaveBeenCalledWith(
      jasmine.any(String),
      jasmine.any(Object)
    );
  });

  it('toHaveBeenCalledWith - using jasmine.objectContaining', () => {
    service.handle('update', { id: 42, name: 'Alice', timestamp: Date.now() });

    expect(service.handle).toHaveBeenCalledWith(
      'update',
      jasmine.objectContaining({ id: 42, name: 'Alice' })
    );
  });

  it('toHaveBeenCalledWith - using jasmine.arrayContaining', () => {
    service.handle(['apple', 'banana', 'cherry', 'date']);

    expect(service.handle).toHaveBeenCalledWith(
      jasmine.arrayContaining(['banana', 'cherry'])
    );
  });

  it('toHaveBeenCalledWith - using jasmine.stringMatching', () => {
    service.handle('ERROR: Connection timeout on server-5');

    expect(service.handle).toHaveBeenCalledWith(
      jasmine.stringMatching(/^ERROR:/)
    );
    expect(service.handle).toHaveBeenCalledWith(
      jasmine.stringMatching('Connection')
    );
  });

  it('toHaveBeenCalledTimes - verifies exact call count', () => {
    service.handle();
    service.handle();
    service.handle();
    expect(service.handle).toHaveBeenCalledTimes(3);
  });

  it('verifying call order with and. and.sequence', () => {
    const spy1 = jasmine.createSpy('first');
    const spy2 = jasmine.createSpy('second');

    spy1();
    spy2();
    spy1();

    // Check that spy1 was called before spy2 at least once
    expect(spy1.calls.first()).toHaveBeenCalledBefore(spy2.calls.first());
  });
});

Accessing Spy Call Data

describe('Inspecting spy calls', () => {
  it('examines individual call arguments using calls.all()', () => {
    const spy = jasmine.createSpy('accumulator');

    spy('first', 1);
    spy('second', 2);
    spy('third', 3);

    const allCalls = spy.calls.all();
    expect(allCalls.length).toBe(3);

    // Access arguments of the first call
    expect(allCalls[0].args).toEqual(['first', 1]);
    expect(allCalls[1].args[0]).toBe('second');
    expect(allCalls[2].args[1]).toBe(3);
  });

  it('uses calls.first(), calls.mostRecent(), calls.count()', () => {
    const spy = jasmine.createSpy('tracker');

    spy('alpha');
    spy('beta');
    spy('gamma');

    expect(spy.calls.first().args).toEqual(['alpha']);
    expect(spy.calls.mostRecent().args).toEqual(['gamma']);
    expect(spy.calls.count()).toBe(3);
  });

  it('uses calls.allArgs() to get an array of argument arrays', () => {
    const spy = jasmine.createSpy('collector');

    spy(1, 2);
    spy(3, 4);
    spy(5, 6);

    const allArgs = spy.calls.allArgs();
    expect(allArgs).toEqual([[1, 2], [3, 4], [5, 6]]);
  });

  it('uses calls.argsFor(index) to get arguments of a specific call', () => {
    const spy = jasmine.createSpy('indexed');

    spy('a', 'b');
    spy('c', 'd');
    spy('e', 'f');

    expect(spy.calls.argsFor(0)).toEqual(['a', 'b']);
    expect(spy.calls.argsFor(2)).toEqual(['e', 'f']);
  });

  it('resets spy tracking data with calls.reset()', () => {
    const spy = jasmine.createSpy('resetDemo');

    spy('call one');
    spy('call two');
    expect(spy).toHaveBeenCalledTimes(2);

    spy.calls.reset();
    expect(spy).not.toHaveBeenCalled();
    expect(spy.calls.count()).toBe(0);
  });
});

Creating Spy Objects with Multiple Spy Methods

describe('Spy objects with jasmine.createSpyObj', () => {
  it('creates an object with multiple spy methods', () => {
    const mockService = jasmine.createSpyObj('ApiClient', [
      'get',
      'post',
      'put',
      'delete'
    ]);

    mockService.get.and.returnValue(Promise.resolve({ data: 'response' }));
    mockService.post.and.callFake((data) => Promise.resolve({ id: 1, ...data }));

    // Now use the mock object in your tests
    const client = new DataManager(mockService);
    client.fetch('/users');

    expect(mockService.get).toHaveBeenCalledWith('/users');
  });

  it('creates spy object with properties as well', () => {
    const mockService = jasmine.createSpyObj('ConfigurableService', {
      // Methods with return values
      'fetch': Promise.resolve({ ok: true }),
      'save': Promise.resolve({ saved: true }),
      // Properties (these become simple values, not spies)
      'baseUrl': 'https://api.example.com',
      'version': '2.1.0'
    }, [
      // Additional spy methods without predefined returns
      'log',
      'notify'
    ]);

    expect(mockService.baseUrl).toBe('https://api.example.com');
    mockService.log('test');
    expect(mockService.log).toHaveBeenCalledWith('test');
  });
});

Testing Asynchronous Code

Modern JavaScript applications rely heavily on asynchronous operations — API calls, file I/O, timers, and promises. Jasmine provides robust mechanisms to test asynchronous code accurately, ensuring tests wait for operations to complete before evaluating expectations.

Testing Promises with async/await

The cleanest approach is to use async/await inside your spec functions. Jasmine detects that the spec function returns a promise and waits for it to resolve:

describe('Async/await testing', () => {
  let dataService;

  beforeEach(() => {
    dataService = {
      fetchUser: (id) => Promise.resolve({ id, name: 'Alice', active: true }),
      fetchUsers: () => Promise.resolve([
        { id: 1, name: 'Alice' },
        { id: 2, name: 'Bob' }
      ]),
      fetchWithError: () => Promise.reject(new Error('Network error'))
    };
  });

  it('should fetch a user by ID using async/await', async () => {
    const user = await dataService.fetchUser(42);
    expect(user.id).toBe(42);
    expect(user.name).toBe('Alice');
    expect(user.active).toBeTruthy();
  });

  it('should fetch multiple users', async () => {
    const users = await dataService.fetchUsers();
    expect(users.length).toBe(2);
    expect(users[0]).toEqual({ id: 1, name: 'Alice' });
  });

  it('should handle rejection using try/catch', async () => {
    try {
      await dataService.fetchWithError();
      // If we reach here, the test should fail
      fail('Expected fetchWithError to throw');
    } catch (error) {
      expect(error.message).toBe('Network error');
    }
  });
});

Using the done() Callback (Legacy Approach)

For callback-based asynchronous code, Jasmine provides the done callback. The spec function receives a done parameter — Jasmine waits until done is called (or a timeout occurs):

describe('Callback-based async testing with done()', () => {
  it('handles callback-based operations', (done) => {
    function fetchData(callback) {
      setTimeout(() => {
        callback(null, { result: 'success' });
      }, 100);
    }

    fetchData((error, data) => {
      expect(error).toBeNull();
      expect(data).toEqual({ result: 'success' });
      done(); // Signal that async work is complete
    });
  });

  it('handles errors in callbacks', (done) => {
    function riskyOperation(callback) {
      setTimeout(() => {
        callback(new Error('Something went wrong'), null);
      }, 50);
    }

    riskyOperation((error, data) => {
      expect(error).toBeDefined();
      expect(error.message).toBe('Something went wrong');
      expect(data).toBeNull();
      done();
    });
  });

  it('fails if done is never called (timeout demonstration)', (done) => {
    // Jasmine's default async timeout is 5 seconds
    // If done() is not called within that time, the spec fails
    // This spec intentionally demonstrates proper usage
    let callbackInvoked = false;

    function slowOperation(cb) {
      setTimeout(() => {
        callbackInvoked = true;
        cb('data');
      }, 10);
    }

    slowOperation((result) => {
      expect(result).toBe('data');
      expect(callbackInvoked).toBe(true);
      done();
    });
  });
});

Using jasmine.clock() for Timer Control

Jasmine's clock feature lets you mock JavaScript timers (setTimeout, setInterval, Date) so you can synchronously control time-dependent code:

describe('Jasmine Clock - timer mocking', () => {
  let timerCallback;

  beforeEach(() => {
    timerCallback = jasmine.createSpy('timerCallback');
    jasmine.clock().install(); // Mock the clock
  });

  afterEach(() => {
    jasmine.clock().uninstall(); // Restore real timers
  });

  it('synchronously triggers setTimeout callbacks', () => {
    setTimeout(() => {
      timerCallback('fired');
    }, 5000);

    // At this point, the callback has NOT been called (time hasn't advanced)
    expect(timerCallback).not.toHaveBeenCalled();

    // Advance the clock by 5000ms
    jasmine.clock().tick(5000);

    // Now the callback has been executed
    expect(timerCallback).toHaveBeenCalledWith('fired');
  });

  it('handles setInterval correctly', () => {
    let counter = 0;

    const intervalId = setInterval(() => {
      counter++;
      timerCallback(counter);
    }, 1000);

    // Tick 3500ms — should trigger 3 interval callbacks
    jasmine.clock().tick(3500);

    expect(timerCallback).toHaveBeenCalledTimes(3);
    expect(timerCallback).toHaveBeenCalledWith(1);
    expect(timerCallback).toHaveBeenCalledWith(2);
    expect(timerCallback).toHaveBeenCalledWith(3);
    expect(counter).toBe(3);

    clearInterval(intervalId);

    // Tick more — no additional calls after clearing
    jasmine.clock().tick(5000);
    expect(timerCallback).toHaveBeenCalledTimes(3);
  });

  it('mocks Date.now() and new Date()', () => {
    const baseDate = new Date(2024, 0, 1, 12, 0, 0);
    jasmine.clock().mockDate(baseDate);

    expect(Date.now()).toBe(baseDate.getTime());

    // Advance time
    jasmine.clock().tick(3600000); // 1 hour in milliseconds
    const newDate = new Date();
    expect(newDate.getHours()).toBe(13); // 12 + 1 = 13
  });
});

Combining Async Patterns

describe('Combined async patterns', () => {
  it('handles promises with clock mocking', async () => {
    jasmine.clock().install();
    const baseTime = new Date(2024, 5, 15).getTime();
    jasmine.clock().mockDate(new Date(baseTime));

    const delayedPromise = () => new Promise((resolve) => {
      setTimeout(() => {
        resolve({ timestamp: Date.now() });
      }, 2000);
    });

    const promise = delayedPromise();

    // Advance clock to trigger the setTimeout
    jasmine.clock().tick(2000);

    const result = await promise;

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles