or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

code-organization-rules.mdcode-style-rules.mdexpectation-rules.mdindex.mdjasmine-matcher-rules.mdpromise-rules.mdspy-rules.mdtest-structure-rules.md
tile.json

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');