or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cli-tools.mdconfiguration.mdindex.mdlow-level-communication.mdmodule-setup.mdpush-notifications.mdtesting.mdupdate-management.md
tile.json

testing.mddocs/

Testing Utilities

Mock implementations and testing utilities for unit testing Angular service worker functionality in development and test environments.

Capabilities

MockServiceWorkerContainer

Mock implementation of the browser's ServiceWorkerContainer for testing service worker interactions.

/**
 * Mock implementation of ServiceWorkerContainer for testing
 * Simulates service worker registration and message handling
 */
class MockServiceWorkerContainer {
  /**
   * Current active service worker instance
   * Set by setupSw() method
   */
  controller: MockServiceWorker | null;
  
  /**
   * Subject for push messages received by the service worker
   * Use to simulate incoming push notifications
   */
  messages: Subject<any>;
  
  /**
   * Subject for notification click events
   * Use to simulate user interactions with notifications
   */
  notificationClicks: Subject<{}>;
  
  /**
   * Mock service worker registration object
   * Created by setupSw() method
   */
  mockRegistration: MockServiceWorkerRegistration | null;
  
  /**
   * Add event listener for service worker events
   * @param event - Event type to listen for
   * @param handler - Event handler function
   */
  addEventListener(event: 'controllerchange' | 'message', handler: Function): void;
  
  /**
   * Remove event listener for service worker events
   * @param event - Event type to remove listener for
   * @param handler - Event handler function to remove
   */
  removeEventListener(event: 'controllerchange' | 'message', handler: Function): void;
  
  /**
   * Mock service worker registration
   * @param url - Service worker script URL (ignored in mock)
   * @returns Promise that resolves immediately
   */
  register(url: string): Promise<void>;
  
  /**
   * Get service worker registration
   * @returns Promise resolving to mock registration object
   */
  getRegistration(): Promise<ServiceWorkerRegistration>;
  
  /**
   * Set up mock service worker with controller
   * @param url - Service worker script URL (default: '/ngsw-worker.js')
   */
  setupSw(url?: string): void;
  
  /**
   * Send message to service worker event listeners
   * @param value - Message data to send
   */
  sendMessage(value: Object): void;
}

Usage Examples:

import { TestBed } from '@angular/core/testing';
import { SwPush, SwUpdate } from '@angular/service-worker';
import { MockServiceWorkerContainer } from '@angular/service-worker/testing';

describe('Service Worker Tests', () => {
  let mockSwContainer: MockServiceWorkerContainer;
  let swPush: SwPush;
  let swUpdate: SwUpdate;
  
  beforeEach(() => {
    mockSwContainer = new MockServiceWorkerContainer();
    
    TestBed.configureTestingModule({
      providers: [
        SwPush,
        SwUpdate,
        {
          provide: 'serviceWorker',
          useValue: mockSwContainer
        }
      ]
    });
    
    swPush = TestBed.inject(SwPush);
    swUpdate = TestBed.inject(SwUpdate);
    
    // Set up mock service worker
    mockSwContainer.setupSw();
  });
  
  it('should handle push messages', () => {
    const receivedMessages: any[] = [];
    
    swPush.messages.subscribe(message => {
      receivedMessages.push(message);
    });
    
    // Simulate push message
    mockSwContainer.sendMessage({
      type: 'PUSH',
      data: { title: 'Test Message', body: 'Test Body' }
    });
    
    expect(receivedMessages).toHaveLength(1);
    expect(receivedMessages[0]).toEqual({
      title: 'Test Message',
      body: 'Test Body'
    });
  });
  
  it('should handle version updates', () => {
    const versionEvents: any[] = [];
    
    swUpdate.versionUpdates.subscribe(event => {
      versionEvents.push(event);
    });
    
    // Simulate version ready event
    mockSwContainer.sendMessage({
      type: 'VERSION_READY',
      currentVersion: { hash: 'abc123' },
      latestVersion: { hash: 'def456' }
    });
    
    expect(versionEvents).toHaveLength(1);
    expect(versionEvents[0].type).toBe('VERSION_READY');
  });
});

MockServiceWorker

Mock implementation of a service worker instance for testing message posting.

/**
 * Mock service worker instance for testing
 * Simulates postMessage functionality
 */
class MockServiceWorker {
  /**
   * URL of the service worker script
   */
  readonly scriptURL: string;
  
  /**
   * Create mock service worker
   * @param mock - Parent mock container (private)
   * @param scriptURL - Service worker script URL (readonly)
   */
  constructor(private mock: MockServiceWorkerContainer, readonly scriptURL: string);
  
  /**
   * Post message to service worker
   * @param value - Message data to post
   */
  postMessage(value: Object): void;
}

MockServiceWorkerRegistration

Mock implementation of ServiceWorkerRegistration for testing push subscriptions.

/**
 * Mock service worker registration for testing
 * Provides mock push manager
 */
class MockServiceWorkerRegistration {
  /**
   * Mock push manager instance
   * Handles push subscription operations
   */
  pushManager: PushManager;
}

MockPushManager

Mock implementation of PushManager for testing push notification subscriptions.

/**
 * Mock push manager for testing push subscriptions
 * Simulates browser push notification API
 */
class MockPushManager {
  /**
   * Get current push subscription
   * @returns Promise resolving to current subscription or null
   */
  getSubscription(): Promise<PushSubscription | null>;
  
  /**
   * Subscribe to push notifications
   * @param options - Push subscription options (ignored in mock)
   * @returns Promise resolving to mock push subscription
   */
  subscribe(options?: PushSubscriptionOptionsInit): Promise<PushSubscription>;
}

MockPushSubscription

Mock implementation of PushSubscription for testing unsubscribe operations.

/**
 * Mock push subscription for testing
 * Simulates browser PushSubscription API
 */
class MockPushSubscription {
  /**
   * Unsubscribe from push notifications
   * @returns Promise resolving to true (always succeeds in mock)
   */
  unsubscribe(): Promise<boolean>;
}

Utility Functions

Helper functions for testing service worker functionality.

/**
 * Patch base64 decoding for Node.js testing environments
 * @param proto - Object with decodeBase64 method to patch
 * @returns Function to unpatch the method
 */
function patchDecodeBase64(proto: {decodeBase64: typeof atob}): () => void;

Usage Examples:

import { SwPush } from '@angular/service-worker';
import { patchDecodeBase64 } from '@angular/service-worker/testing';

describe('SwPush', () => {
  let swPush: SwPush;
  let unpatch: () => void;
  
  beforeEach(() => {
    // Patch for Node.js environment
    unpatch = patchDecodeBase64(SwPush.prototype);
    
    // Set up component/service
    swPush = TestBed.inject(SwPush);
  });
  
  afterEach(() => {
    // Restore original method
    unpatch();
  });
  
  it('should decode VAPID keys', async () => {
    const subscription = await swPush.requestSubscription({
      serverPublicKey: 'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
    });
    
    expect(subscription).toBeDefined();
  });
});

Testing Patterns

Complete Service Worker Test Setup

import { TestBed } from '@angular/core/testing';
import { SwPush, SwUpdate } from '@angular/service-worker';
import { 
  MockServiceWorkerContainer,
  patchDecodeBase64 
} from '@angular/service-worker/testing';

describe('Service Worker Integration', () => {
  let mockSwContainer: MockServiceWorkerContainer;
  let swPush: SwPush;
  let swUpdate: SwUpdate;
  let unpatchDecodeBase64: () => void;
  
  beforeEach(() => {
    mockSwContainer = new MockServiceWorkerContainer();
    unpatchDecodeBase64 = patchDecodeBase64(SwPush.prototype);
    
    TestBed.configureTestingModule({
      providers: [
        SwPush,
        SwUpdate,
        {
          provide: 'serviceWorker',
          useValue: mockSwContainer
        }
      ]
    });
    
    swPush = TestBed.inject(SwPush);
    swUpdate = TestBed.inject(SwUpdate);
    mockSwContainer.setupSw();
  });
  
  afterEach(() => {
    unpatchDecodeBase64();
  });
  
  // Test cases here...
});

Testing Push Notifications

it('should subscribe to push notifications', async () => {
  const subscription = await swPush.requestSubscription({
    serverPublicKey: 'test-key'
  });
  
  expect(subscription).toBeDefined();
  expect(swPush.isEnabled).toBe(true);
});

it('should receive push messages', () => {
  const messages: any[] = [];
  swPush.messages.subscribe(msg => messages.push(msg));
  
  mockSwContainer.sendMessage({
    type: 'PUSH',
    data: { title: 'Test', body: 'Message' }
  });
  
  expect(messages).toHaveLength(1);
  expect(messages[0]).toEqual({ title: 'Test', body: 'Message' });
});

it('should handle notification clicks', () => {
  const clicks: any[] = [];
  swPush.notificationClicks.subscribe(click => clicks.push(click));
  
  mockSwContainer.sendMessage({
    type: 'NOTIFICATION_CLICK',
    data: {
      action: 'open',
      notification: { title: 'Test', body: 'Click' }
    }
  });
  
  expect(clicks).toHaveLength(1);
  expect(clicks[0].action).toBe('open');
});

Testing App Updates

it('should handle version updates', () => {
  const events: any[] = [];
  swUpdate.versionUpdates.subscribe(event => events.push(event));
  
  // Simulate version detected
  mockSwContainer.sendMessage({
    type: 'VERSION_DETECTED',
    version: { hash: 'new-version' }
  });
  
  // Simulate version ready
  mockSwContainer.sendMessage({
    type: 'VERSION_READY',
    currentVersion: { hash: 'old-version' },
    latestVersion: { hash: 'new-version' }
  });
  
  expect(events).toHaveLength(2);
  expect(events[0].type).toBe('VERSION_DETECTED');
  expect(events[1].type).toBe('VERSION_READY');
});

it('should handle unrecoverable states', () => {
  const states: any[] = [];
  swUpdate.unrecoverable.subscribe(state => states.push(state));
  
  mockSwContainer.sendMessage({
    type: 'UNRECOVERABLE_STATE',
    reason: 'Cache corruption'
  });
  
  expect(states).toHaveLength(1);
  expect(states[0].reason).toBe('Cache corruption');
});

it('should check for updates', async () => {
  const promise = swUpdate.checkForUpdate();
  
  // Simulate response
  mockSwContainer.sendMessage({
    type: 'OPERATION_COMPLETED',
    nonce: jasmine.any(Number),
    result: true
  });
  
  const result = await promise;
  expect(result).toBe(true);
});

Testing Service Availability

it('should detect service worker support', () => {
  expect(swPush.isEnabled).toBe(true);
  expect(swUpdate.isEnabled).toBe(true);
});

it('should handle disabled service worker', () => {
  // Test with no service worker
  TestBed.overrideProvider('serviceWorker', { useValue: undefined });
  
  const swPushDisabled = TestBed.inject(SwPush);
  const swUpdateDisabled = TestBed.inject(SwUpdate);
  
  expect(swPushDisabled.isEnabled).toBe(false);
  expect(swUpdateDisabled.isEnabled).toBe(false);
});

Node.js Testing Environment

When running tests in Node.js (e.g., with Jest or Karma), you may need to polyfill browser APIs:

// test-setup.ts
global.atob = global.atob || ((str: string) => Buffer.from(str, 'base64').toString('binary'));
global.btoa = global.btoa || ((str: string) => Buffer.from(str, 'binary').toString('base64'));

// Mock navigator.serviceWorker if needed
if (typeof navigator === 'undefined') {
  (global as any).navigator = {
    serviceWorker: new MockServiceWorkerContainer()
  };
}

Best Practices

  1. Always use mock container: Don't test against real service workers in unit tests
  2. Patch base64 methods: Use patchDecodeBase64 for Node.js environments
  3. Clean up subscriptions: Unsubscribe from observables in afterEach
  4. Test error scenarios: Simulate network failures and permission denials
  5. Mock push subscriptions: Use MockPushManager for push notification tests
  6. Simulate realistic events: Send properly formatted service worker messages