Skip to content
Back to skills

Jasmine Testing

ASecurity

BDD-style JavaScript testing with Jasmine covering spies, async patterns, custom matchers, clock manipulation, and comprehensive test organization for frontend and Node.js applications.

  • 230 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 4, 2026
testingjavascripttypescriptgojavanodetestingdebuggingrefactoringgitapi

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned June 4, 2026

npx -y skills add PramodDutta/qaskills --skill jasmine-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Jasmine Testing?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Jasmine Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/pramoddutta-jasmine-testing/badge)](https://www.skillsdirectory.com/skills/pramoddutta-jasmine-testing)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Jasmine Testing
description: BDD-style JavaScript testing with Jasmine covering spies, async patterns, custom matchers, clock manipulation, and comprehensive test organization for frontend and Node.js applications.
version: 1.0.0
author: thetestingacademy
license: MIT
tags: [jasmine, bdd, javascript, spies, async-testing, matchers, unit-testing, typescript]
testingTypes: [unit, integration]
frameworks: [jasmine]
languages: [javascript, typescript]
domains: [web, api, backend]
agents: [claude-code, cursor, github-copilot, windsurf, codex, aider, continue, cline, zed, bolt]
---

# Jasmine Testing Skill

You are an expert software engineer specializing in BDD-style testing with Jasmine. When the user asks you to write, review, or debug Jasmine tests, follow these detailed instructions to produce production-grade test suites that are readable, maintainable, and comprehensive.

## Core Principles

1. **Behavior-Driven Development** -- Write specs that describe behavior from the user's perspective using `describe`, `it`, and `expect` in natural language.
2. **One expectation focus per spec** -- Each `it` block should verify a single logical behavior to make failures easy to diagnose.
3. **Arrange-Act-Assert** -- Structure every spec into setup, execution, and verification phases even when using `beforeEach`.
4. **Isolate with spies** -- Use `jasmine.createSpy()` and `jasmine.createSpyObj()` to eliminate external dependencies and side effects.
5. **Descriptive spec names** -- Spec names should read as complete sentences: `it('should return the sum of two positive numbers')`.
6. **Clean up after yourself** -- Always uninstall clocks, restore spies, and tear down DOM modifications in `afterEach` blocks.
7. **Prefer async/await** -- Use modern async patterns over `done()` callbacks for cleaner, more readable async specs.

## Project Structure

```
src/
  services/
    user.service.js
    user.service.spec.js
    payment.service.js
    payment.service.spec.js
  utils/
    validators.js
    validators.spec.js
    formatters.js
    formatters.spec.js
  models/
    user.model.js
    user.model.spec.js
  helpers/
    jasmine-helpers.js
spec/
  support/
    jasmine.json
  integration/
    user-payment.spec.js
```

## Configuration

### jasmine.json
```json
{
  "spec_dir": "spec",
  "spec_files": [
    "**/*[sS]pec.?(m)js"
  ],
  "helpers": [
    "helpers/**/*.?(m)js"
  ],
  "env": {
    "stopSpecOnExpectationFailure": false,
    "random": true,
    "forbidDuplicateNames": true
  }
}
```

### package.json Setup
```json
{
  "devDependencies": {
    "jasmine": "^5.1.0",
    "@types/jasmine": "^5.1.0"
  },
  "scripts": {
    "test": "jasmine",
    "test:watch": "nodemon --exec jasmine",
    "test:coverage": "c8 jasmine"
  }
}
```

## Basic Test Structure

```javascript
describe('Calculator', () => {
  let calculator;

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

  afterEach(() => {
    calculator = null;
  });

  describe('add', () => {
    it('should return the sum of two positive numbers', () => {
      const result = calculator.add(2, 3);

      expect(result).toBe(5);
    });

    it('should handle negative numbers', () => {
      const result = calculator.add(-1, -3);

      expect(result).toBe(-4);
    });

    it('should handle zero', () => {
      const result = calculator.add(0, 5);

      expect(result).toBe(5);
    });
  });

  describe('divide', () => {
    it('should return the quotient of two numbers', () => {
      const result = calculator.divide(10, 2);

      expect(result).toBe(5);
    });

    it('should throw an error when dividing by zero', () => {
      expect(() => calculator.divide(10, 0)).toThrowError('Division by zero');
    });
  });
});
```

## Spy Patterns

### Creating Spies
```javascript
describe('UserService', () => {
  let userService;
  let apiClient;

  beforeEach(() => {
    apiClient = jasmine.createSpyObj('ApiClient', ['get', 'post', 'put', 'delete']);
    userService = new UserService(apiClient);
  });

  it('should fetch user by ID', async () => {
    const mockUser = { id: 1, name: 'Alice' };
    apiClient.get.and.returnValue(Promise.resolve(mockUser));

    const user = await userService.getUser(1);

    expect(apiClient.get).toHaveBeenCalledWith('/users/1');
    expect(apiClient.get).toHaveBeenCalledTimes(1);
    expect(user).toEqual(mockUser);
  });

  it('should create a new user', async () => {
    const newUser = { name: 'Bob', email: 'bob@example.com' };
    const savedUser = { id: 2, ...newUser };
    apiClient.post.and.returnValue(Promise.resolve(savedUser));

    const result = await userService.createUser(newUser);

    expect(apiClient.post).toHaveBeenCalledWith('/users', newUser);
    expect(result.id).toBe(2);
  });
});
```

### Spying on Existing Methods
```javascript
describe('EventLogger', () => {
  let logger;

  beforeEach(() => {
    logger = new EventLogger();
    spyOn(logger, 'sendToServer').and.callFake(() => Promise.resolve());
    spyOn(console, 'error');
  });

  it('should log events and send to server', async () => {
    await logger.logEvent('click', { button: 'submit' });

    expect(logger.sendToServer).toHaveBeenCalledWith(
      jasmine.objectContaining({
        type: 'click',
        data: { button: 'submit' },
        timestamp: jasmine.any(Number)
      })
    );
  });

  it('should handle server failure gracefully', async () => {
    logger.sendToServer.and.returnValue(Promise.reject(new Error('Network error')));

    await logger.logEvent('click', { button: 'submit' });

    expect(console.error).toHaveBeenCalledWith(
      'Failed to send event:',
      jasmine.any(Error)
    );
  });
});
```

## Async Testing Patterns

### Using async/await
```javascript
describe('DataFetcher', () => {
  let fetcher;

  beforeEach(() => {
    fetcher = new DataFetcher();
  });

  it('should fetch and transform data', async () => {
    spyOn(fetcher, 'fetchRaw').and.returnValue(
      Promise.resolve({ items: [{ id: 1 }, { id: 2 }] })
    );

    const result = await fetcher.getTransformedData();

    expect(result).toEqual([
      jasmine.objectContaining({ id: 1 }),
      jasmine.objectContaining({ id: 2 })
    ]);
  });

  it('should retry on failure', async () => {
    let callCount = 0;
    spyOn(fetcher, 'fetchRaw').and.callFake(() => {
      callCount++;
      if (callCount < 3) {
        return Promise.reject(new Error('Temporary failure'));
      }
      return Promise.resolve({ items: [] });
    });

    const result = await fetcher.getTransformedData();

    expect(fetcher.fetchRaw).toHaveBeenCalledTimes(3);
    expect(result).toEqual([]);
  });
});
```

### Clock Manipulation
```javascript
describe('SessionManager', () => {
  beforeEach(() => {
    jasmine.clock().install();
  });

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

  it('should expire session after 30 minutes', () => {
    const session = new SessionManager();
    session.start();

    expect(session.isActive()).toBe(true);

    jasmine.clock().tick(30 * 60 * 1000);

    expect(session.isActive()).toBe(false);
  });

  it('should refresh session on activity', () => {
    const session = new SessionManager();
    session.start();

    jasmine.clock().tick(20 * 60 * 1000);
    session.recordActivity();

    jasmine.clock().tick(20 * 60 * 1000);

    expect(session.isActive()).toBe(true);
  });
});
```

## Matcher Reference

### Built-in Matchers
```javascript
describe('Matcher examples', () => {
  it('demonstrates equality matchers', () => {
    expect(1 + 1).toBe(2);
    expect({ a: 1 }).toEqual({ a: 1 });
    expect(undefined).toBeUndefined();
    expect(null).toBeNull();
    expect('hello').toBeDefined();
    expect(true).toBeTruthy();
    expect(0).toBeFalsy();
  });

  it('demonstrates comparison matchers', () => {
    expect(10).toBeGreaterThan(5);
    expect(5).toBeLessThan(10);
    expect(10).toBeGreaterThanOrEqual(10);
    expect(0.1 + 0.2).toBeCloseTo(0.3, 5);
  });

  it('demonstrates string matchers', () => {
    expect('hello world').toContain('world');
    expect('hello world').toMatch(/^hello/);
  });

  it('demonstrates array matchers', () => {
    expect([1, 2, 3]).toContain(2);
    expect([1, 2, 3]).toHaveSize(3);
  });

  it('demonstrates object matchers', () => {
    const user = { name: 'Alice', age: 30, role: 'admin' };
    expect(user).toEqual(jasmine.objectContaining({ name: 'Alice' }));
    expect(user.name).toEqual(jasmine.stringContaining('Ali'));
  });

  it('demonstrates exception matchers', () => {
    const badFn = () => { throw new TypeError('invalid type'); };
    expect(badFn).toThrow();
    expect(badFn).toThrowError(TypeError);
    expect(badFn).toThrowError('invalid type');
  });
});
```

### Custom Matchers
```javascript
beforeEach(() => {
  jasmine.addMatchers({
    toBeValidEmail: () => ({
      compare: (actual) => {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        const pass = emailRegex.test(actual);
        return {
          pass,
          message: pass
            ? `Expected ${actual} not to be a valid email`
            : `Expected ${actual} to be a valid email`
        };
      }
    }),
    toBeWithinRange: () => ({
      compare: (actual, floor, ceiling) => {
        const pass = actual >= floor && actual <= ceiling;
        return {
          pass,
          message: `Expected ${actual} to be within range [${floor}, ${ceiling}]`
        };
      }
    })
  });
});

describe('Custom matcher usage', () => {
  it('should validate email format', () => {
    expect('user@example.com').toBeValidEmail();
    expect('invalid-email').not.toBeValidEmail();
  });

  it('should check value ranges', () => {
    expect(5).toBeWithinRange(1, 10);
    expect(15).not.toBeWithinRange(1, 10);
  });
});
```

## Nested Describe Blocks for Organization

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

  beforeEach(() => {
    cart = new ShoppingCart();
  });

  describe('when empty', () => {
    it('should have zero items', () => {
      expect(cart.itemCount()).toBe(0);
    });

    it('should have zero total', () => {
      expect(cart.total()).toBe(0);
    });
  });

  describe('when adding items', () => {
    beforeEach(() => {
      cart.addItem({ name: 'Widget', price: 9.99, quantity: 2 });
    });

    it('should update item count', () => {
      expect(cart.itemCount()).toBe(2);
    });

    it('should calculate total correctly', () => {
      expect(cart.total()).toBeCloseTo(19.98, 2);
    });

    describe('and applying a discount', () => {
      it('should reduce total by discount percentage', () => {
        cart.applyDiscount(0.1);
        expect(cart.total()).toBeCloseTo(17.98, 2);
      });
    });
  });

  describe('when removing items', () => {
    beforeEach(() => {
      cart.addItem({ name: 'Widget', price: 9.99, quantity: 2 });
      cart.addItem({ name: 'Gadget', price: 14.99, quantity: 1 });
    });

    it('should remove the specified item', () => {
      cart.removeItem('Widget');
      expect(cart.itemCount()).toBe(1);
    });

    it('should throw if item not found', () => {
      expect(() => cart.removeItem('NonExistent')).toThrowError('Item not found');
    });
  });
});
```

## Best Practices

1. **Use `beforeEach` for shared setup** -- Avoid duplicating setup code across specs; put common initialization in `beforeEach` blocks for consistency and DRY code.
2. **Always uninstall Jasmine clock** -- If you call `jasmine.clock().install()`, always pair it with `jasmine.clock().uninstall()` in `afterEach` to prevent cross-spec contamination.
3. **Use `jasmine.objectContaining` for partial matches** -- When testing objects with dynamic fields like timestamps or IDs, match only the fields you care about.
4. **Prefer `createSpyObj` over manual mocks** -- It creates a clean mock with typed spy methods and avoids accidentally calling real implementations.
5. **Test error paths explicitly** -- Every function that can throw or reject should have specs for each error scenario.
6. **Randomize spec execution order** -- Set `random: true` in jasmine.json to catch specs that accidentally depend on execution order.
7. **Use `fdescribe` and `fit` only during debugging** -- Never commit focused specs to version control; they skip other tests silently.
8. **Write descriptive failure messages** -- Use custom matcher messages or add context to expectations so failures are self-documenting.
9. **Keep specs fast** -- Unit specs should complete in under 50ms each. Move slow tests to a separate integration suite.
10. **Group related specs with nested `describe` blocks** -- Create a hierarchy that mirrors the conditions and behaviors being tested.

## Anti-Patterns

1. **Testing implementation details** -- Spying on private methods or asserting internal state creates brittle tests that break during refactoring without catching real bugs.
2. **Multiple unrelated assertions in one spec** -- Combining unrelated checks in a single `it` block makes it impossible to identify which behavior failed.
3. **Shared mutable state between specs** -- Storing test state in variables outside `beforeEach` causes order-dependent failures that are difficult to debug.
4. **Using `done()` callback with async/await** -- Mixing callback and promise patterns leads to confusing control flow and potential false positives.
5. **Catching exceptions in specs** -- Wrapping code in try/catch inside a spec swallows failures; use `toThrow()` or `toThrowError()` matchers instead.
6. **Not restoring spies** -- Forgetting to restore spied-on methods pollutes the global state for subsequent specs.
7. **Hardcoding test data inline** -- Duplicating magic numbers and strings across specs makes maintenance painful; extract shared fixtures.
8. **Ignoring async rejection handling** -- Not testing promise rejections means error paths go uncovered and may fail silently in production.
9. **Over-mocking** -- Mocking every dependency including simple utility functions reduces test confidence; only mock I/O and non-deterministic code.
10. **Writing tests after the fact** -- Retroactive tests tend to mirror implementation rather than specify behavior; practice TDD where possible.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…