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

spy-rules.mddocs/

Spy Management Rules

Rules that enforce best practices for Jasmine spy usage and management to ensure reliable test isolation and clear spy behavior.

Capabilities

named-spy

Encourages the use of named spies to improve test readability and debugging capabilities.

/**
 * Rule: named-spy
 * Encourages naming spies for better debugging
 * @config [0|1|2] - Rule severity level
 */
'jasmine/named-spy': [0|1|2]

Examples:

// ❌ Potentially unclear - anonymous spy
spyOn(service, 'getData');

// ✅ Better - named spy for clarity
spyOn(service, 'getData').and.callFake(function mockGetData(id) {
  return { id: id, name: 'Test User' };
});

// ✅ Good - using jasmine.createSpy with name
const mockCallback = jasmine.createSpy('callback spy');

no-assign-spyon

Prevents assignment of spyOn return values to variables, which can lead to confusion about spy behavior and lifecycle.

/**
 * Rule: no-assign-spyon
 * Disallows assigning spyOn results to variables
 * @config [0|1|2] - Rule severity level
 */
'jasmine/no-assign-spyon': [0|1|2]

Examples:

// ❌ Bad - assigning spyOn result
const spy = spyOn(service, 'getData');
spy.and.returnValue('mocked data');

// ✅ Good - chain spy configuration
spyOn(service, 'getData').and.returnValue('mocked data');

// ✅ Good - use createSpy for reusable spies
const reusableSpy = jasmine.createSpy('getData spy');
service.getData = reusableSpy;

no-unsafe-spy

Prevents potentially unsafe spy patterns that could lead to unreliable tests or unexpected behavior.

/**
 * Rule: no-unsafe-spy
 * Disallows unsafe spy usage patterns
 * @config [0|1|2] - Rule severity level
 */
'jasmine/no-unsafe-spy': [0|1|2]

Common unsafe patterns:

  • Spying on non-existent methods
  • Spying on properties that aren't functions
  • Creating spies without proper cleanup

Examples:

// ❌ Bad - spying on non-existent method
spyOn(service, 'nonExistentMethod'); // Runtime error

// ❌ Bad - spying on property that's not a function
spyOn(service, 'config'); // config is an object, not a function

// ✅ Good - spy on existing methods
spyOn(service, 'getData').and.returnValue('test data');

// ✅ Good - check method exists before spying
if (typeof service.optionalMethod === 'function') {
  spyOn(service, 'optionalMethod');
}

Rule Configuration

All spy management 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/named-spy: 0           # Optional - style preference
  jasmine/no-assign-spyon: 0     # Optional - some patterns are valid
  jasmine/no-unsafe-spy: 1       # Warning - helps catch errors

Common Spy Patterns

Creating and Configuring Spies

describe('Service with dependencies', function() {
  let service, mockDependency;
  
  beforeEach(function() {
    // Create mock dependency
    mockDependency = jasmine.createSpyObj('dependency', [
      'fetchData',
      'saveData',
      'validateData'
    ]);
    
    service = new Service(mockDependency);
  });
  
  it('should call dependency methods', function() {
    // Configure spy behavior
    mockDependency.fetchData.and.returnValue('test data');
    mockDependency.validateData.and.returnValue(true);
    
    // Execute code under test
    const result = service.processData(123);
    
    // Verify spy interactions
    expect(mockDependency.fetchData).toHaveBeenCalledWith(123);
    expect(mockDependency.validateData).toHaveBeenCalledWith('test data');
    expect(result).toBe('processed: test data');
  });
});

Spy Cleanup and Isolation

describe('Component with external dependencies', function() {
  let originalMethod;
  
  beforeEach(function() {
    // Store original method for restoration
    originalMethod = ExternalService.prototype.apiCall;
    
    // Create spy
    spyOn(ExternalService.prototype, 'apiCall')
      .and.returnValue(Promise.resolve('mocked response'));
  });
  
  afterEach(function() {
    // Jasmine automatically restores spies, but explicit cleanup is good practice
    ExternalService.prototype.apiCall = originalMethod;
  });
  
  it('should use mocked external service', async function() {
    const component = new Component();
    const result = await component.performAction();
    
    expect(ExternalService.prototype.apiCall).toHaveBeenCalled();
    expect(result).toContain('mocked response');
  });
});

Advanced Spy Configurations

describe('Complex spy scenarios', function() {
  it('should handle different call scenarios', function() {
    const mockService = jasmine.createSpyObj('service', ['process']);
    
    // Different return values for different calls
    mockService.process.and.returnValues(
      'first call result',
      'second call result',
      'default result'
    );
    
    expect(mockService.process()).toBe('first call result');
    expect(mockService.process()).toBe('second call result');
    expect(mockService.process()).toBe('default result');
  });
  
  it('should use custom fake implementation', function() {
    const calculator = { add: function(a, b) { return a + b; } };
    
    spyOn(calculator, 'add').and.callFake(function(a, b) {
      console.log(`Adding ${a} + ${b}`);
      return a + b + 1; // Modified behavior for testing
    });
    
    const result = calculator.add(2, 3);
    expect(result).toBe(6); // 2 + 3 + 1
    expect(calculator.add).toHaveBeenCalledWith(2, 3);
  });
});