npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@halverscheid-fiae.de/angular-testing-factory

v2.0.1

Published

Type-safe Angular service mocking with zero drift guarantee for Angular 21+

Readme

@halverscheid-fiae.de/angular-testing-factory

Revolutionary type-safe Angular service mocking for Angular 21+
Zero Mock Drift™ guarantee with compile-time validation

npm version npm downloads license bundle size TypeScript Angular Vitest

🎯 Why This Library?

The Problem: Angular testing is painful. Manual mocks break when services change, TypeScript can't catch mock drift, and every new service needs tons of boilerplate.

The Solution: This library provides compile-time safe mocking with zero configuration for 90% of use cases, and zero mock drift for your custom services.

✨ Revolutionary Features

  • 🎯 Zero Mock Drift™: TypeScript satisfies catches mock inconsistencies at compile-time
  • One-Line Providers: provideHttpClientMock() - Done!
  • 🚀 Automated CI/CD: Semantic versioning with automatic NPM publishing
  • 🧪 100% Test Coverage: All 148 tests pass with comprehensive coverage
  • 🎯 Vitest Native: Fast, modern testing with Vitest 4+
  • 🔄 Override Anything: Per-test customization with the Factory Pattern
  • 🛡️ 100% Type Safe: Full IntelliSense and compile-time validation
  • 📦 Angular 21+ Native: Signals, Standalone Components, modern inject()
  • ⚙️ Zero Config: Works out-of-the-box with sensible defaults

🚀 Quick Start

Installation

npm install --save-dev @halverscheid-fiae.de/angular-testing-factory

90% Use Case: Preset Collections

import { describe, it, expect, beforeEach } from 'vitest';
import { TestBed } from '@angular/core/testing';
import { 
  provideHttpClientMock, 
  provideRouterMock, 
  provideMatDialogMock 
} from '@halverscheid-fiae.de/angular-testing-factory';

describe('MyComponent', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClientMock(),    // ← HttpClient with sensible defaults
        provideRouterMock(),        // ← Router with navigation mocks
        provideMatDialogMock()      // ← MatDialog with dialog mocks
      ]
    });
  });

  it('should work perfectly', () => {
    // Your component gets fully mocked dependencies!
  });
});

🔥 The Revolutionary Factory Pattern

Create Once, Override Everywhere

import { vi } from 'vitest';
import { of, signal } from 'rxjs';

// 1. Create your factory ONCE (in test-setup.ts or similar)
const provideMyBusinessServiceMock = createServiceProviderFactory(MyBusinessService, {
  calculateRevenue: vi.fn(() => of(1000)),
  processPayment: vi.fn(() => Promise.resolve(false)),
  currentUser: signal({ id: 1, name: 'Test User' }),
  isLoading: signal(false)
});

// 2. Use everywhere with per-test overrides:
describe('Revenue Tests', () => {
  it('should handle high revenue', () => {
    TestBed.configureTestingModule({
      providers: [
        provideMyBusinessServiceMock({ 
          calculateRevenue: vi.fn(() => of(50000)) // ← Override just this!
        })
      ]
    });
    // Test high revenue scenario
  });

  it('should handle payment failures', () => {
    TestBed.configureTestingModule({
      providers: [
        provideMyBusinessServiceMock({ 
          processPayment: vi.fn(() => Promise.reject('Card declined'))
        })
      ]
    });
    // Test payment failure scenario
  });

  it('should use sensible defaults', () => {
    TestBed.configureTestingModule({
      providers: [
        provideMyBusinessServiceMock() // ← All defaults, no overrides
      ]
    });
    // Test normal flow
  });
});

🚀 Quick Migration Guide

From Jest to Vitest

// ❌ Before: Jest
import { jest } from '@jest/globals';

beforeEach(() => {
  const mockFn = jest.fn();
});

// ✅ After: Vitest
import { vi } from 'vitest';

beforeEach(() => {
  const mockFn = vi.fn();
});

From Manual Window Mocking

// ❌ Before: Manual & Error-prone
beforeEach(() => {
  (global as any).window = {
    innerWidth: 1024,
    addEventListener: vi.fn(),
    // Missing tons of properties...
  };
});

// ✅ After: Complete & Type-safe
beforeEach(() => {
  const { providers, cleanup } = provideCompleteWindowMock({
    overrides: { innerWidth: 1024 },
    mockGlobal: true
  });
  
  TestBed.configureTestingModule({ providers });
  cleanup = windowCleanup;
});

From Direct Injection Errors

// ❌ Before: Runtime Injection Errors
TestBed.inject(Window); // NG0201 Error!
TestBed.inject(Document); // NG0201 Error!

// ✅ After: Proper Token Usage
import { WINDOW_TOKEN, DOCUMENT_TOKEN } from '@halverscheid-fiae.de/angular-testing-factory';

TestBed.inject(WINDOW_TOKEN); // ✅ Works!
TestBed.inject(DOCUMENT_TOKEN); // ✅ Works!

✨ New: Angular Core Extensions

// 🆕 Complete test setup in one line
TestBed.configureTestingModule({
  providers: provideAngularCoreMocks({
    activatedRoute: {
      snapshot: { params: { id: '123' } }
    },
    window: {
      innerWidth: 1920,
      localStorage: mockStorage()
    }
  })
});

// 🆕 Individual providers for specific needs
TestBed.configureTestingModule({
  providers: [
    provideActivatedRouteMock({
      params: of({ productId: '456' }),
      queryParams: of({ tab: 'details' })
    }),
    provideFormBuilderMock(),
    provideElementRefMock<HTMLInputElement>({
      nativeElement: mockInputElement
    })
  ]
});

The Magic: Zero Mock Drift™

interface MyBusinessService {
  calculateRevenue(): Observable<number>;
  processPayment(amount: number): Promise<boolean>;
  currentUser: Signal<User>;
  isLoading: WritableSignal<boolean>;
}

// ✅ This will catch ANY drift at compile-time:
const provideMyBusinessServiceMock = createServiceProviderFactory(MyBusinessService, {
  calculateRevenue: vi.fn(() => of(1000)),
  // ❌ If you forget a method → TypeScript error!
  // ❌ If you add wrong method → TypeScript error!  
  // ❌ If return type changes → TypeScript error!
  // ❌ If service interface changes → TypeScript error!
});
import { vi } from 'vitest';
import { of } from 'rxjs';
import { provideHttpClientMock } from '@halverscheid-fiae.de/angular-testing-factory';

// Mock HTTP calls with specific responses
TestBed.configureTestingModule({
  providers: [
    provideHttpClientMock({
      get: vi.fn(() => of({ data: 'custom response' })),
      post: vi.fn(() => of({ success: true }))
    })
  ]
});

📖 API Reference

Core Functions

  • createMockProvider<T>(token, mockService) - Creates Angular Provider for mocks
  • createMockService<T>(defaults, overrides) - Creates type-safe mock objects

Preset Providers

Angular Common

  • provideHttpClientMock(overrides?) - HttpClient Mock
  • provideRouterMock(overrides?) - Router Mock
  • provideLocationMock(overrides?) - Location Mock
  • provideAngularCommonMocks() - All Common Services

Angular Core Extensions 🆕

  • provideActivatedRouteMock(overrides?) - ActivatedRoute Mock (Params, QueryParams, Data)
  • provideFormBuilderMock(overrides?) - FormBuilder Mock (Reactive Forms)
  • provideDomSanitizerMock(overrides?) - DomSanitizer Mock (Security Bypass)

Browser API Mocks 🆕

  • provideElementRefMock<T>(overrides?) - ElementRef Mock with Generic Support
  • provideDocumentMock(overrides?) - Document Mock (DOM Operations)
  • provideWindowMock(overrides?) - Window Mock for Token-based Injection
  • setupGlobalWindowMock(overrides?) - Global Window Mock for Direct Access 🔥
  • provideCompleteWindowMock(options?) - Combined Token + Global Window Mock 🔥

🌟 Advanced Window Mocking

Problem Solved: Components using window directly vs. WINDOW_TOKEN injection

// ❌ Traditional approach: Only works for token-based injection
providers: [provideWindowMock({ innerWidth: 800 })]

// ✅ New approach: Covers both use cases
const { providers, cleanup } = provideCompleteWindowMock({
  overrides: { innerWidth: 800 },
  mockGlobal: true  // Also mocks global window object
});

TestBed.configureTestingModule({ providers });
// cleanup() restores original window after tests

Common Use Cases & Solutions:

❌ Problem: ɵNotFound: NG0201: No provider found for Window

// Wrong - Window is not an Angular token
windowMock = TestBed.inject(Window);

✅ Solution 1: Use WINDOW_TOKEN for token-based injection

import { WINDOW_TOKEN, provideWindowMock } from '@halverscheid-fiae.de/angular-testing-factory';

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [provideWindowMock({ innerWidth: 1200 })]
  });
  
  windowMock = TestBed.inject(WINDOW_TOKEN); // ✅ Correct token
});

✅ Solution 2: Complete Window mocking (recommended)

import { provideCompleteWindowMock, WINDOW_TOKEN } from '@halverscheid-fiae.de/angular-testing-factory';

describe('MyComponent', () => {
  let cleanup: (() => void) | undefined;

  beforeEach(() => {
    const result = provideCompleteWindowMock({
      overrides: { innerWidth: 1200, location: { href: 'http://test.com' } },
      mockGlobal: true // Mocks both token and global access
    });
    
    cleanup = result.cleanup;

    TestBed.configureTestingModule({
      providers: result.providers
    });
    
    // Both work now:
    windowMock = TestBed.inject(WINDOW_TOKEN); // Token-based
    // window.innerWidth also works in component code
  });

  afterEach(() => {
    cleanup?.(); // Clean up global window mock
  });
});

✅ Solution 3: Component injection pattern

// In your component - use token-based injection:
import { inject } from '@angular/core';
import { WINDOW_TOKEN } from '@halverscheid-fiae.de/angular-testing-factory';

@Component({...})
export class MyComponent {
  private window = inject(WINDOW_TOKEN);
  
  onResize() {
    const width = this.window.innerWidth; // ✅ Testable
  }
}

Use Cases:

  • Token-based: inject(WINDOW_TOKEN) in Angular services
  • Direct access: window.innerWidth in legacy components
  • Global mocking: Testing code that accesses window directly

Convenience Bundles 🆕

  • provideAngularCoreMocks(overrides?) - All Critical Angular Core Services
  • provideAngularCommonMocks() - Legacy Common Services Bundle

Angular Material

  • provideMatDialogMock(overrides?) - MatDialog Mock
  • provideMatSnackBarMock(overrides?) - MatSnackBar Mock
  • provideAngularMaterialMocks() - All Material Services

🛠️ Custom Services

3-Line Rule for New Services

import { vi } from 'vitest';
import { of } from 'rxjs';

// 1. Define service defaults
const MY_SERVICE_DEFAULTS = {
  getData: vi.fn(() => of([])),
  saveData: vi.fn(() => Promise.resolve())
} satisfies Partial<MyService>;

// 2. Create factory
const createMockMyService = (overrides = {}) => 
  createMockService(MY_SERVICE_DEFAULTS, overrides);

// 3. Export provider
export const provideMyServiceMock = (overrides = {}) => 
  createMockProvider(MyService, createMockMyService(overrides));

� Common Issues & Solutions

Window/Document Injection Problems

Error: ɵNotFound: NG0201: No provider found for Window

Quick Fix:

// ❌ Wrong
TestBed.inject(Window);
TestBed.inject(Document);

// ✅ Correct
import { WINDOW_TOKEN, DOCUMENT_TOKEN } from '@halverscheid-fiae.de/angular-testing-factory';

TestBed.inject(WINDOW_TOKEN);
TestBed.inject(DOCUMENT_TOKEN);

Complete Solution:

import { provideCompleteWindowMock } from '@halverscheid-fiae.de/angular-testing-factory';

describe('MyComponent', () => {
  let cleanup: (() => void) | undefined;

  beforeEach(() => {
    const { providers, cleanup: windowCleanup } = provideCompleteWindowMock({
      mockGlobal: true
    });
    cleanup = windowCleanup;

    TestBed.configureTestingModule({
      providers: [...providers, /* other providers */]
    });
  });

  afterEach(() => cleanup?.());
});

FormBuilder Validation Errors

Error: TypeError: control.setParent is not a function

Root Cause: Vitest needs to properly mock Angular Forms globally to avoid conflicts.

Complete Solution:

  1. Create vitest.setup.ts in your project root:
// vitest.setup.ts - Global Angular Forms Mock
import { vi } from 'vitest';
import '@angular/compiler';
import { TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';

// Initialize Angular TestBed
TestBed.initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);

vi.mock('@angular/forms', () => {
  class MockFormControl {
    constructor(formState?: any, validatorOrOpts?: any, asyncValidator?: any) {
      this.value = Array.isArray(formState) ? formState[0] : formState;
      this.valid = true;
      this.invalid = false;
      this.errors = null;
      this.setValue = vi.fn();
      this.patchValue = vi.fn();
      this.reset = vi.fn();
      this.setParent = vi.fn(); // CRITICAL: This method must exist!
      // Additional FormControl properties as needed
    }
    
    value: any;
    valid: boolean;
    invalid: boolean;
    errors: any;
    setValue: any;
    patchValue: any;
    reset: any;
    setParent: any;
  }

  class MockFormGroup {
    constructor(controlsConfig?: any, options?: any) {
      this.controls = {};
      this.value = {};
      
      if (controlsConfig) {
        Object.keys(controlsConfig).forEach(key => {
          const config = controlsConfig[key];
          this.controls[key] = new MockFormControl(config);
          // Ensure parent relationship
          if (this.controls[key].setParent) {
            this.controls[key].setParent = vi.fn();
          }
        });
      }
      
      this.setValue = vi.fn();
      this.patchValue = vi.fn();
      this.get = vi.fn((path: string) => this.controls[path] || null);
      this.setParent = vi.fn();
    }
    
    controls: any;
    value: any;
    setValue: any;
    patchValue: any;
    get: any;
    setParent: any;
  }

  class MockFormBuilder {
    control(formState?: any, validatorOrOpts?: any, asyncValidator?: any) {
      return new MockFormControl(formState, validatorOrOpts, asyncValidator);
    }
    group(controlsConfig?: any, options?: any) {
      return new MockFormGroup(controlsConfig, options);
    }
  }

  return {
    FormControl: MockFormControl,
    FormGroup: MockFormGroup,
    FormBuilder: MockFormBuilder
  };
});
  1. Update vitest.config.ts:
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'happy-dom', // or 'jsdom'
    setupFiles: ['./vitest.setup.ts'],
    // ... rest of your configuration
  }
});
  1. Alternative: Use our pre-configured provider:
import { provideAngularCoreMocks } from '@halverscheid-fiae.de/angular-testing-factory';

TestBed.configureTestingModule({
  providers: [
    provideAngularCoreMocks(), // Includes setParent-aware FormBuilder
    // ... your other providers
  ]
});

SignalStore Testing

Error: Cannot spy on SignalStore methods directly

Solution:

🔍 SignalStore Testing

import { vi } from 'vitest';

// ❌ This does NOT work with SignalStores:
const spy = vi.spyOn(store.myService, 'getData'); // Error!

// ✅ Correct approach for SignalStores:
const mockService = TestBed.inject(MyService); // After TestBed setup
const spy = vi.spyOn(mockService, 'getData');

🔧 Troubleshooting Guide

Missing Provider Errors

  • Use WINDOW_TOKEN instead of Window
  • Use DOCUMENT_TOKEN instead of Document
  • Ensure all mocks are provided in TestBed configuration

Mock Not Working

  • Check import statements - ensure you're importing from the correct package
  • Verify TypeScript configuration allows proper jest mocking
  • Use provideCompleteWindowMock for complex window scenarios

Performance Issues

  • Use setupGlobalWindowMock only when necessary
  • Clean up global mocks in afterEach() hooks
  • Consider using token-based injection for better performance

💡 Why This Library?

Before (Traditional Approach)

import { vi } from 'vitest';

// ❌ Error-prone, lots of boilerplate, mock drift
const mockService = {
  getData: vi.fn(),
  setData: vi.fn(),
  // Forgotten methods lead to runtime errors
};

TestBed.configureTestingModule({
  providers: [
    { provide: MyService, useValue: mockService }
  ]
});

After (With Angular Testing Factory)

import { vi } from 'vitest';
import { of } from 'rxjs';

// ✅ Type-safe, 3-line rule, zero mock drift
TestBed.configureTestingModule({
  providers: [
    provideMyServiceMock({
      getData: vi.fn(() => of(customData))
    })
  ]
});

🏗️ Architecture

@halverscheid-fiae.de/angular-testing-factory/
├── 🏭 core/           # Universal Mock Factory System
├── 📦 presets/        # Ready-to-use Service Mocks
├── 🎯 types/          # TypeScript Definitions
└── 🛠️ utils/          # Test Helper Utilities

🎯 Problem Solved

Traditional Angular testing suffers from:

  • Mock Drift: Service changes break tests at runtime
  • Boilerplate: Repetitive mock setup code
  • Type Safety: Missing compile-time guarantees
  • SignalStore Issues: Complex injection context handling

This library provides:

  • Compile-time Safety: TypeScript satisfies catches errors early
  • DRY Principle: Reusable factories eliminate duplication
  • Modern Angular: Built for standalone components and signals
  • Developer Experience: 3-line rule for maximum productivity

📋 Requirements

  • Angular 21+
  • TypeScript 5.0+
  • Vitest 4+
  • RxJS 7+

🧪 Setting Up Vitest with Angular

Installation

npm install --save-dev vitest @vitest/ui happy-dom @angular/compiler @angular/platform-browser-dynamic

Configuration

vitest.config.ts:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'happy-dom',
    include: ['src/**/*.spec.ts'],
    setupFiles: ['./vitest.setup.ts']
  }
});

vitest.setup.ts:

import { vi } from 'vitest';
import '@angular/compiler';
import { TestBed } from '@angular/core/testing';
import { 
  BrowserDynamicTestingModule, 
  platformBrowserDynamicTesting 
} from '@angular/platform-browser-dynamic/testing';

// Initialize Angular TestBed environment
TestBed.initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting()
);

package.json:

{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest run --coverage"
  }
}

🤝 Contributing

Contributions welcome! Please read our Contributing Guide.

Development Workflow

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes following our commit conventions (see below)
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📋 Commit Message Conventions

This project uses automatic semantic versioning based on commit messages. Please follow these conventions:

Version Bumping Rules

🔧 Patch Release (1.0.01.0.1):

git commit -m "fix: resolve HttpClient mock timeout issue"
git commit -m "docs: update installation instructions"  
git commit -m "chore: update dependencies"

✨ Minor Release (1.0.01.1.0):

git commit -m "feat: add MatSnackBar mock provider"
git commit -m "feat(presets): add Angular Forms mock collection"

💥 Major Release (1.0.02.0.0):

git commit -m "feat!: redesign API for better TypeScript inference"
git commit -m "refactor!: remove deprecated functions"

# Or with BREAKING CHANGE in body:
git commit -m "feat: redesign API for better TypeScript inference

BREAKING CHANGE: createMockProvider now requires explicit type parameter"

Commit Types

  • feat: New features → Minor version
  • fix: Bug fixes → Patch version
  • docs: Documentation → Patch version
  • style: Code style → Patch version
  • refactor: Code refactoring → Patch version
  • test: Adding tests → Patch version
  • chore: Maintenance → Patch version

Breaking Changes

Add BREAKING CHANGE: in commit body OR use ! after type for Major version:

# Option 1: ! suffix (recommended)
git commit -m "feat!: remove deprecated createLegacyMock function"  
git commit -m "refactor!: change API structure"

# Option 2: BREAKING CHANGE in body
git commit -m "refactor: improve type inference

BREAKING CHANGE: Generic type parameters order changed"

🤖 Automatic Publishing

When your PR is merged to main:

  1. Version automatically bumped based on commit messages
  2. Git tag created (e.g., v1.2.3)
  3. NPM package published automatically
  4. No manual steps required!

Example Workflow:

  • You commit: feat: add new provider for Angular Router
  • After merge: 1.0.01.1.0 + NPM publish + Git tag v1.1.0

🐛 Issues

Found a bug? Please report it.

📄 License

MIT © Christian Halverscheid

🚀 Made with ❤️ for the Angular Community

This library was created to solve real-world testing challenges in enterprise Angular applications. Your feedback and contributions make it better!