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

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

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