CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-eslint-plugin-jasmine

ESLint plugin providing 23 linting rules specifically designed for Jasmine testing framework to enforce best practices and catch common mistakes

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

jasmine-matcher-rules.mddocs/

Jasmine-Specific Matcher Rules

Rules that encourage usage of Jasmine-specific matchers over generic alternatives to improve test expressiveness and error messaging.

Capabilities

prefer-jasmine-matcher

Encourages the use of Jasmine's built-in matchers instead of generic equality checks, providing better error messages and more expressive test code.

/**
 * Rule: prefer-jasmine-matcher
 * Prefer Jasmine-specific matchers over generic alternatives
 * @config [0|1|2] - Rule severity level
 */
'jasmine/prefer-jasmine-matcher': [0|1|2]

Examples:

// ❌ Discouraged - generic equality checks
expect(value === null).toBe(true);
expect(array.length === 0).toBe(true);
expect(typeof result === 'undefined').toBe(true);
expect(fn.toString().indexOf('async') !== -1).toBe(true);

// ✅ Better - Jasmine-specific matchers
expect(value).toBeNull();
expect(array).toEqual([]);
expect(result).toBeUndefined();
expect(fn).toContain('async');

// ❌ Discouraged - complex boolean checks
expect(user.isActive && user.hasPermission).toBe(true);
expect(!error.isEmpty()).toBe(true);

// ✅ Better - specific matchers with clear intent
expect(user.isActive).toBe(true);
expect(user.hasPermission).toBe(true);
expect(error.isEmpty()).toBe(false);

prefer-toHaveBeenCalledWith

Encourages the use of toHaveBeenCalledWith for spy assertions instead of generic spy call checking.

/**
 * Rule: prefer-toHaveBeenCalledWith
 * Prefer toHaveBeenCalledWith over generic spy call checks
 * @config [0|1|2] - Rule severity level
 */
'jasmine/prefer-toHaveBeenCalledWith': [0|1|2]

Examples:

// ❌ Discouraged - generic call checking
expect(spy.calls.count()).toBe(1);
expect(spy.calls.argsFor(0)).toEqual(['arg1', 'arg2']);

// ❌ Discouraged - manual argument verification
expect(spy).toHaveBeenCalled();
expect(spy.calls.mostRecent().args[0]).toBe('expectedArg');

// ✅ Better - specific matcher with arguments
expect(spy).toHaveBeenCalledWith('arg1', 'arg2');
expect(spy).toHaveBeenCalledTimes(1);

// ✅ Good - multiple call verification
expect(spy).toHaveBeenCalledWith('first call');
expect(spy).toHaveBeenCalledWith('second call');
expect(spy).toHaveBeenCalledTimes(2);

prefer-toBeUndefined

Controls preference for toBeUndefined() matcher usage over other undefined checking patterns.

/**
 * Rule: prefer-toBeUndefined
 * Control preference for toBeUndefined matcher usage
 * @config [0|1|2, mode] - Rule severity and preference mode
 * @param mode 'always' | 'never' - When to prefer toBeUndefined
 * @default mode 'always'
 */
'jasmine/prefer-toBeUndefined': [0|1|2, 'always' | 'never']

Configuration Examples:

// Always prefer toBeUndefined
'jasmine/prefer-toBeUndefined': [1, 'always']

// Never prefer toBeUndefined (use generic checks)
'jasmine/prefer-toBeUndefined': [1, 'never']

Examples:

// With 'always' mode:
// ❌ Discouraged
expect(value).toBe(undefined);
expect(typeof value).toBe('undefined');

// ✅ Preferred
expect(value).toBeUndefined();

// With 'never' mode:
// ❌ Discouraged
expect(value).toBeUndefined();

// ✅ Preferred
expect(value).toBe(undefined);

Rule Configuration

All Jasmine-specific matcher rules support standard ESLint severity levels:

  • 0 or "off" - Disable the rule
  • 1 or "warn" - Enable as warning
  • 2 or "error" - Enable as error (fails lint)

Recommended Settings:

rules:
  jasmine/prefer-jasmine-matcher: 1           # Warning - improves expressiveness
  jasmine/prefer-toHaveBeenCalledWith: 1      # Warning - better spy testing
  jasmine/prefer-toBeUndefined: 0             # Optional - style preference

Jasmine Matcher Benefits

Better Error Messages

describe('Error message examples', function() {
  it('should demonstrate clear error messages', function() {
    const user = { name: 'John', age: 25 };
    
    // ❌ Generic check - unclear error message
    // Error: Expected false to be true
    expect(user.name === 'Jane').toBe(true);
    
    // ✅ Specific matcher - clear error message
    // Error: Expected 'John' to be 'Jane'
    expect(user.name).toBe('Jane');
    
    // ❌ Array length check - unclear
    // Error: Expected false to be true
    expect(user.hobbies.length > 0).toBe(true);
    
    // ✅ Better - shows actual vs expected clearly
    // Error: Expected [] not to be empty
    expect(user.hobbies).not.toEqual([]);
  });
});

Comprehensive Matcher Examples

describe('Jasmine matcher showcase', function() {
  let user, mockService;
  
  beforeEach(function() {
    user = {
      name: 'Alice',
      age: 30,
      email: 'alice@example.com',
      hobbies: ['reading', 'cycling'],
      profile: {
        isPublic: true,
        lastLogin: new Date('2023-01-15')
      }
    };
    
    mockService = jasmine.createSpyObj('service', ['save', 'validate', 'notify']);
  });
  
  describe('value matchers', function() {
    it('should use appropriate matchers for different value types', function() {
      // Null/undefined checks
      expect(user.name).toBeDefined();
      expect(user.middleName).toBeUndefined();
      expect(user.deletedAt).toBeNull();
      
      // Type checks
      expect(user.age).toEqual(jasmine.any(Number));
      expect(user.hobbies).toEqual(jasmine.any(Array));
      expect(user.profile).toEqual(jasmine.any(Object));
      
      // String matchers
      expect(user.email).toContain('@');
      expect(user.email).toMatch(/^[a-z]+@[a-z]+\.[a-z]+$/);
      
      // Numeric comparisons
      expect(user.age).toBeGreaterThan(18);
      expect(user.age).toBeLessThanOrEqual(100);
      expect(user.hobbies.length).toBeCloseTo(2, 0);
      
      // Array/object matchers
      expect(user.hobbies).toContain('reading');
      expect(user.hobbies).toHaveSize(2);
      expect(user.profile).toHaveProperty('isPublic');
      expect(user.profile).toHaveProperty('isPublic', true);
    });
  });
  
  describe('spy matchers', function() {
    it('should use appropriate spy matchers', function() {
      const userData = { name: 'Bob', email: 'bob@test.com' };
      
      mockService.validate.and.returnValue(true);
      mockService.save.and.returnValue({ id: 123 });
      
      // Perform actions
      const isValid = mockService.validate(userData);
      if (isValid) {
        const result = mockService.save(userData);
        mockService.notify('user-saved', result);
      }
      
      // Spy assertions with specific matchers
      expect(mockService.validate).toHaveBeenCalled();
      expect(mockService.validate).toHaveBeenCalledWith(userData);
      expect(mockService.validate).toHaveBeenCalledTimes(1);
      
      expect(mockService.save).toHaveBeenCalledWith(userData);
      expect(mockService.save).toHaveBeenCalledBefore(mockService.notify);
      
      expect(mockService.notify).toHaveBeenCalledWith('user-saved', { id: 123 });
      
      // Verify call order
      expect(mockService.validate).toHaveBeenCalledBefore(mockService.save);
      expect(mockService.save).toHaveBeenCalledBefore(mockService.notify);
    });
    
    it('should verify spy call patterns', function() {
      mockService.save('data1');
      mockService.save('data2');
      mockService.save('data3');
      
      // Verify multiple calls
      expect(mockService.save).toHaveBeenCalledTimes(3);
      expect(mockService.save).toHaveBeenCalledWith('data1');
      expect(mockService.save).toHaveBeenCalledWith('data2');
      expect(mockService.save).toHaveBeenCalledWith('data3');
      
      // Verify call arguments
      expect(mockService.save.calls.argsFor(0)).toEqual(['data1']);
      expect(mockService.save.calls.argsFor(1)).toEqual(['data2']);
      expect(mockService.save.calls.mostRecent().args).toEqual(['data3']);
    });
  });
  
  describe('async matchers', function() {
    it('should use async-specific matchers', async function() {
      const successPromise = Promise.resolve('success');
      const failurePromise = Promise.reject(new Error('failure'));
      
      // Promise state matchers
      await expectAsync(successPromise).toBeResolved();
      await expectAsync(successPromise).toBeResolvedTo('success');
      
      await expectAsync(failurePromise).toBeRejected();
      await expectAsync(failurePromise).toBeRejectedWith(jasmine.any(Error));
      await expectAsync(failurePromise).toBeRejectedWithError('failure');
    });
  });
});

Migration Guide

Converting Generic Checks to Jasmine Matchers

// Before: Generic boolean checks
expect(value === null).toBe(true);
expect(value !== null).toBe(true);
expect(value === undefined).toBe(true);
expect(typeof value === 'string').toBe(true);
expect(array.length === 0).toBe(true);
expect(array.length > 0).toBe(true);

// After: Jasmine-specific matchers
expect(value).toBeNull();
expect(value).not.toBeNull();
expect(value).toBeUndefined();
expect(value).toEqual(jasmine.any(String));
expect(array).toEqual([]);
expect(array).not.toEqual([]);

// Before: Manual spy verification
expect(spy.calls.count()).toBe(1);
expect(spy.calls.argsFor(0)[0]).toBe('arg1');
expect(spy.calls.argsFor(0)[1]).toBe('arg2');

// After: Specific spy matchers
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith('arg1', 'arg2');

// Before: Complex object/array checks
expect(obj.hasOwnProperty('prop')).toBe(true);
expect(array.indexOf('item') !== -1).toBe(true);
expect(str.indexOf('substring') >= 0).toBe(true);

// After: Expressive matchers
expect(obj).toHaveProperty('prop');
expect(array).toContain('item');
expect(str).toContain('substring');

docs

code-organization-rules.md

code-style-rules.md

expectation-rules.md

index.md

jasmine-matcher-rules.md

promise-rules.md

spy-rules.md

test-structure-rules.md

tile.json