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

promise-rules.mddocs/

Promise Handling Rules

Rules that enforce proper handling of promises and asynchronous code in Jasmine tests to prevent test flakiness and ensure reliable async testing.

Capabilities

no-promise-without-done-fail

Prevents promises in tests without proper error handling through done.fail callbacks, ensuring async test failures are properly caught and reported.

/**
 * Rule: no-promise-without-done-fail
 * Ensures promises in tests have proper error handling with done.fail
 * @config [0|1|2] - Rule severity level
 */
'jasmine/no-promise-without-done-fail': [0|1|2]

Examples:

// ❌ Bad - promise without done.fail
it('should handle async operation', function(done) {
  asyncOperation()
    .then(function(result) {
      expect(result).toBe('success');
      done();
    });
  // Missing .catch(done.fail) - test may hang on rejection
});

// ❌ Bad - multiple promises without proper error handling
it('should handle multiple async operations', function(done) {
  Promise.all([
    asyncOperation1(),
    asyncOperation2()
  ]).then(function(results) {
    expect(results.length).toBe(2);
    done();
  });
  // Missing error handling
});

// ✅ Good - promise with done.fail
it('should handle async operation', function(done) {
  asyncOperation()
    .then(function(result) {
      expect(result).toBe('success');
    })
    .then(done)
    .catch(done.fail);
});

// ✅ Good - async/await pattern (preferred for modern Jasmine)
it('should handle async operation', async function() {
  const result = await asyncOperation();
  expect(result).toBe('success');
});

prefer-promise-strategies

Encourages the use of modern promise handling strategies and Jasmine's async utilities over callback-based patterns.

/**
 * Rule: prefer-promise-strategies
 * Encourages modern async testing patterns over callback-based approaches
 * @config [0|1|2] - Rule severity level
 */
'jasmine/prefer-promise-strategies': [0|1|2]

Examples:

// ❌ Discouraged - callback-based async testing
it('should handle async operation', function(done) {
  asyncOperation(function(error, result) {
    if (error) {
      done.fail(error);
      return;
    }
    expect(result).toBe('success');
    done();
  });
});

// ✅ Better - promise-based with expectAsync
it('should handle async operation', function() {
  const promise = asyncOperation();
  return expectAsync(promise).toBeResolvedTo('success');
});

// ✅ Best - async/await pattern
it('should handle async operation', async function() {
  const result = await asyncOperation();
  expect(result).toBe('success');
});

// ✅ Good - expectAsync for promise state testing
it('should reject invalid input', function() {
  const promise = processInvalidData();
  return expectAsync(promise).toBeRejectedWith('Invalid input');
});

Rule Configuration

Both promise handling 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/no-promise-without-done-fail: 1    # Warning - prevents hanging tests
  jasmine/prefer-promise-strategies: 1       # Warning - encourages modern patterns

Modern Async Testing Patterns

Using async/await

describe('UserService async operations', function() {
  let userService, mockApiClient;
  
  beforeEach(function() {
    mockApiClient = jasmine.createSpyObj('apiClient', ['get', 'post', 'put', 'delete']);
    userService = new UserService(mockApiClient);
  });
  
  describe('fetchUser', function() {
    it('should fetch user by id', async function() {
      const expectedUser = { id: 123, name: 'John Doe' };
      mockApiClient.get.and.returnValue(Promise.resolve(expectedUser));
      
      const result = await userService.fetchUser(123);
      
      expect(mockApiClient.get).toHaveBeenCalledWith('/users/123');
      expect(result).toEqual(expectedUser);
    });
    
    it('should handle fetch errors', async function() {
      const error = new Error('User not found');
      mockApiClient.get.and.returnValue(Promise.reject(error));
      
      await expectAsync(userService.fetchUser(999)).toBeRejectedWith(error);
      expect(mockApiClient.get).toHaveBeenCalledWith('/users/999');
    });
  });
  
  describe('createUser', function() {
    it('should create user successfully', async function() {
      const userData = { name: 'Jane Smith', email: 'jane@example.com' };
      const createdUser = { id: 456, ...userData };
      mockApiClient.post.and.returnValue(Promise.resolve(createdUser));
      
      const result = await userService.createUser(userData);
      
      expect(mockApiClient.post).toHaveBeenCalledWith('/users', userData);
      expect(result).toEqual(createdUser);
    });
    
    it('should validate user data before creating', async function() {
      const invalidUserData = { name: '', email: 'invalid-email' };
      
      await expectAsync(userService.createUser(invalidUserData))
        .toBeRejectedWithError('Invalid user data');
        
      expect(mockApiClient.post).not.toHaveBeenCalled();
    });
  });
});

Using expectAsync for Promise Testing

describe('Promise state testing', function() {
  let asyncService;
  
  beforeEach(function() {
    asyncService = new AsyncService();
  });
  
  it('should resolve with correct data', function() {
    const promise = asyncService.fetchData(123);
    
    return expectAsync(promise).toBeResolvedTo({ id: 123, data: 'test' });
  });
  
  it('should reject with specific error', function() {
    const promise = asyncService.fetchData(-1);
    
    return expectAsync(promise).toBeRejectedWith('Invalid ID');
  });
  
  it('should handle timeout scenarios', function() {
    const slowPromise = asyncService.slowOperation();
    
    return expectAsync(slowPromise).toBePending();
  });
  
  // Multiple promise assertions
  it('should handle multiple async operations', function() {
    const promise1 = asyncService.operation1();
    const promise2 = asyncService.operation2();
    
    return Promise.all([
      expectAsync(promise1).toBeResolved(),
      expectAsync(promise2).toBeResolved()
    ]);
  });
});

Legacy Pattern with Proper Error Handling

// When you must use done callback pattern
describe('Legacy async patterns', function() {
  it('should handle callback-based API', function(done) {
    legacyAsyncFunction(function(error, result) {
      if (error) {
        done.fail(error);
        return;
      }
      
      expect(result).toBeDefined();
      expect(result.status).toBe('success');
      done();
    });
  });
  
  it('should handle promise with done pattern', function(done) {
    promiseBasedFunction()
      .then(function(result) {
        expect(result).toBeTruthy();
        return anotherPromiseFunction(result);
      })
      .then(function(finalResult) {
        expect(finalResult).toBe('expected');
      })
      .then(done)
      .catch(done.fail); // Critical for proper error handling
  });
});

Complex Async Scenarios

describe('Complex async workflows', function() {
  let workflowService, mockSteps;
  
  beforeEach(function() {
    mockSteps = {
      validate: jasmine.createSpy('validate').and.returnValue(Promise.resolve(true)),
      process: jasmine.createSpy('process').and.returnValue(Promise.resolve({ processed: true })),
      save: jasmine.createSpy('save').and.returnValue(Promise.resolve({ id: 123 }))
    };
    
    workflowService = new WorkflowService(mockSteps);
  });
  
  it('should execute complete workflow', async function() {
    const inputData = { name: 'test', value: 42 };
    
    const result = await workflowService.executeWorkflow(inputData);
    
    expect(mockSteps.validate).toHaveBeenCalledWith(inputData);
    expect(mockSteps.process).toHaveBeenCalledWith(inputData);
    expect(mockSteps.save).toHaveBeenCalledWith({ processed: true });
    expect(result.success).toBe(true);
    expect(result.id).toBe(123);
  });
  
  it('should handle workflow step failures', async function() {
    mockSteps.process.and.returnValue(Promise.reject(new Error('Processing failed')));
    
    const inputData = { name: 'test', value: 42 };
    
    await expectAsync(workflowService.executeWorkflow(inputData))
      .toBeRejectedWithError('Processing failed');
      
    expect(mockSteps.validate).toHaveBeenCalled();
    expect(mockSteps.process).toHaveBeenCalled();
    expect(mockSteps.save).not.toHaveBeenCalled();
  });
  
  it('should handle parallel operations', async function() {
    const operations = [
      workflowService.operation1(),
      workflowService.operation2(),
      workflowService.operation3()
    ];
    
    const results = await Promise.all(operations);
    
    expect(results).toHaveSize(3);
    results.forEach(result => {
      expect(result.success).toBe(true);
    });
  });
});

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