| |

Angular 102 🅰️ Testing — TestBed and Dependency Injection

⏱️ Estimated read: 13 min | |

The previous chapters covered the testing infrastructure: Jasmine, Karma, TestBed, and ComponentFixture. They showed how to write component tests and service tests. This chapter goes deeper into the TestBed itself — the tool that configures the dependency injection context for every test. Understanding TestBed is understanding how Angular’s dependency injection works in the testing environment.

TestBed is a dynamic testing module that mirrors the structure of an @NgModule. It has imports, declarations, providers, and schemas. It creates an injector that provides the dependencies to the components and services under test. The injector is hierarchical: the TestBed injector is the root, and each component’s injector is a child. The TestBed also provides tools for overriding dependencies after the module is configured, which is how tests replace real services with mocks.

Key point: TestBed is a global singleton. It is reset between tests automatically by the Angular testing framework. The configureTestingModule method configures the module for the current test. The inject method retrieves a dependency from the injector. The overrideProvider, overrideComponent, and overrideModule methods modify the configuration before the component is created. The createComponent method creates the component and its fixture.


Why TestBed matters

Angular’s dependency injection is hierarchical. A component declares its dependencies in the constructor or with inject. The framework resolves those dependencies from the injector. In the testing environment, the TestBed is the injector.

The dependency problem. A component depends on a service. The service depends on the HTTP client. The HTTP client depends on the platform. In production, the framework wires all of this together. In the test, the TestBed does. The configureTestingModule method provides the dependencies. The TestBed.inject method retrieves them.

The isolation problem. A test should not depend on the real HTTP client, the real database, or the real file system. The TestBed allows the test to replace these dependencies with mocks. The overrideProvider method replaces a provider. The providers array provides a mock directly.

The module problem. A component that uses MatButtonModule, ReactiveFormsModule, or any other module needs those modules to be available in the test. The imports array of the configureTestingModule makes them available.

The template problem. A component’s template may use pipes, directives, and child components. The declarations array (or imports for standalone components) makes them available. Without them, the template fails to compile.

The trade-off. The TestBed adds overhead. The configureTestingModule and compileComponents calls take time. For a test suite with hundreds of tests, the overhead adds up. The TestBed can be configured once in a beforeEach block and reused. The TestBed is reset automatically between tests.


a. Configuring the Test Module

The configureTestingModule method configures the testing module. It takes the same configuration as an @NgModule.

import { TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [CounterComponent],
    providers: [CounterService],
  }).compileComponents();
});

The imports array declares the standalone components, directives, and pipes that the test uses. The providers array declares the services that the test uses. The compileComponents method compiles the templates asynchronously. The method returns a promise, so the await keyword is used.

For a component that uses a module, the module is added to the imports array.

await TestBed.configureTestingModule({
  imports: [ReactiveFormsModule, MatButtonModule],
  providers: [UserService],
}).compileComponents();

For a component that uses a child component, the child is added to the imports array (if standalone) or the declarations array (if not).

await TestBed.configureTestingModule({
  imports: [CounterComponent, ChildComponent],
}).compileComponents();

The configureTestingModule method can be called without compileComponents. The compileComponents method is only needed when the component’s template uses templateUrl or styleUrls. For inline templates, the compilation happens automatically.


b. Injecting Dependencies

The TestBed.inject method retrieves a dependency from the injector. It is the testing equivalent of the inject function.

import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';

describe('UserService', () => {
  let service: UserService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [UserService],
    });
    service = TestBed.inject(UserService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

The inject method retrieves the service instance from the injector. The service is instantiated with its dependencies. The test calls the service’s methods and checks the results.

The inject method can be used to retrieve any token: a service, a component, a directive, a pipe, or an InjectionToken.

import { InjectionToken } from '@angular/core';

export const API_URL = new InjectionToken<string>('API_URL');

TestBed.configureTestingModule({
  providers: [
    { provide: API_URL, useValue: 'https://api.example.com' },
  ],
});

const apiUrl = TestBed.inject(API_URL);
expect(apiUrl).toBe('https://api.example.com');

The TestBed.inject method is used for services and tokens. For components, the TestBed.createComponent method is used. The createComponent method creates the component and its fixture.

The TestBed.inject method can also be used inside a component’s constructor to retrieve the dependencies. The inject function works in the injection context, which the TestBed provides.


c. Overriding Dependencies

The TestBed provides methods for overriding dependencies after the module is configured. The overrideProvider method replaces a provider. The overrideComponent method overrides a component’s configuration. The overrideModule method overrides a module’s configuration.

The overrideProvider method is the most common. It replaces a provider with a mock.

import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [UserService],
  });

  const spy = jasmine.createSpyObj('UserService', ['getUsers']);
  spy.getUsers.and.returnValue(of([]));

  TestBed.overrideProvider(UserService, { useValue: spy });
});

The overrideProvider method is called after configureTestingModule. The useValue property provides the mock. The mock replaces the real service in the injector.

The overrideComponent method overrides a component’s template or providers.

TestBed.overrideComponent(CounterComponent, {
  set: {
    template: '<p>Overridden template</p>',
  },
});

The set property replaces the component’s configuration. The template property overrides the template. This is useful when the real template is complex and the test only needs a simple version.

The overrideModule method overrides a module’s configuration.

TestBed.overrideModule(MatButtonModule, {
  set: {
    declarations: [],
  },
});

The overrideModule method is used less often than the other two. It is useful when a module’s configuration is problematic in the test environment.

The overrideTemplate method is a shorthand for overrideComponent with a template.

TestBed.overrideTemplate(CounterComponent, '<p>{{ count() }}</p>');

The overrideTemplate method replaces the template with the given string. This is the quickest way to simplify a component’s template in a test.


Complete Example Session

This session demonstrates the TestBed configuration, injection, and overriding on a small application.

// ============================================
// PART 1: THE SERVICE
// ============================================

// user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface User {
  id: number;
  name: string;
}

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users');
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`/api/users/${id}`);
  }
}

// ============================================
// PART 2: THE COMPONENT
// ============================================

// user-list.component.ts
import { Component, inject, signal } from '@angular/core';
import { UserService, User } from './user.service';

@Component({
  selector: 'app-user-list',
  template: `
    <ul>
      @for (user of users(); track user.id) {
        <li>{{ user.name }}</li>
      }
    </ul>
  `,
})
export class UserListComponent {
  private userService = inject(UserService);
  users = signal<User[]>([]);

  ngOnInit(): void {
    this.userService.getUsers().subscribe((users) => this.users.set(users));
  }
}

// ============================================
// PART 3: THE SERVICE TEST WITH TESTBED
// ============================================

// user.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
import { UserService, User } from './user.service';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
      ],
    });
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();
  });

  it('should fetch users', () => {
    const mockUsers: User[] = [{ id: 1, name: 'Alice' }];
    service.getUsers().subscribe((users) => {
      expect(users).toEqual(mockUsers);
    });
    const req = httpMock.expectOne('/api/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);
  });
});

// ============================================
// PART 4: THE COMPONENT TEST WITH OVERRIDE PROVIDER
// ============================================

// user-list.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { UserListComponent } from './user-list.component';
import { UserService } from './user.service';

describe('UserListComponent', () => {
  let fixture: ComponentFixture<UserListComponent>;
  let userService: jasmine.SpyObj<UserService>;

  beforeEach(async () => {
    const spy = jasmine.createSpyObj('UserService', ['getUsers']);
    spy.getUsers.and.returnValue(of([{ id: 1, name: 'Alice' }]));

    await TestBed.configureTestingModule({
      imports: [UserListComponent],
      providers: [{ provide: UserService, useValue: spy }],
    }).compileComponents();

    fixture = TestBed.createComponent(UserListComponent);
    userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
    fixture.detectChanges();
  });

  it('should display the users', () => {
    const items = fixture.nativeElement.querySelectorAll('li');
    expect(items.length).toBe(1);
    expect(items[0].textContent).toContain('Alice');
  });
});

// ============================================
// PART 5: THE OVERRIDE PROVIDER PATTERN
// ============================================

import { TestBed } from '@angular/core/testing';
import { UserListComponent } from './user-list.component';
import { UserService } from './user.service';
import { of } from 'rxjs';

describe('UserListComponent with overrideProvider', () => {
  let fixture: ComponentFixture<UserListComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [UserListComponent],
      providers: [UserService],
    }).compileComponents();

    const spy = jasmine.createSpyObj('UserService', ['getUsers']);
    spy.getUsers.and.returnValue(of([{ id: 1, name: 'Alice' }]));

    TestBed.overrideProvider(UserService, { useValue: spy });

    fixture = TestBed.createComponent(UserListComponent);
    fixture.detectChanges();
  });

  it('should display the users', () => {
    const items = fixture.nativeElement.querySelectorAll('li');
    expect(items.length).toBe(1);
  });
});

// ============================================
// PART 6: THE OVERRIDE TEMPLATE PATTERN
// ============================================

import { TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';

describe('CounterComponent with overridden template', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CounterComponent],
    }).compileComponents();

    TestBed.overrideTemplate(CounterComponent, '<p>Simple template</p>');

    const fixture = TestBed.createComponent(CounterComponent);
    fixture.detectChanges();

    expect(fixture.nativeElement.textContent).toContain('Simple template');
  });
});

// ============================================
// PART 7: THE INJECTION TOKEN
// ============================================

import { InjectionToken } from '@angular/core';

export const API_URL = new InjectionToken<string>('API_URL');

TestBed.configureTestingModule({
  providers: [
    { provide: API_URL, useValue: 'https://api.example.com' },
  ],
});

const apiUrl = TestBed.inject(API_URL);
expect(apiUrl).toBe('https://api.example.com');

// ============================================
// PART 8: THE INJECT FUNCTION IN THE TEST
// ============================================

import { inject } from '@angular/core';

it('should use the inject function', () => {
  TestBed.configureTestingModule({
    providers: [{ provide: API_URL, useValue: 'https://api.example.com' }],
  });

  const apiUrl = TestBed.runInInjectionContext(() => inject(API_URL));
  expect(apiUrl).toBe('https://api.example.com');
});

// ============================================
// PART 9: THE PROVIDER VARIATIONS
// ============================================

// useValue
{ provide: UserService, useValue: mockUserService }

// useClass
{ provide: UserService, useClass: MockUserService }

// useFactory
{ provide: UserService, useFactory: () => new MockUserService() }

// useExisting
{ provide: UserService, useExisting: RealUserService }

// ============================================
// PART 10: THE TESTBED CHECKLIST
// ============================================

// 1. Configure the testing module with imports and providers
// 2. Use compileComponents for external templates
// 3. Use TestBed.inject to retrieve services
// 4. Use overrideProvider to replace dependencies
// 5. Use overrideTemplate to simplify templates
// 6. Use overrideComponent for more complex overrides
// 7. Use TestBed.runInInjectionContext for the inject function
// 8. Reset the TestBed between tests
// 9. Keep the tests focused
// 10. Run the tests in CI

The ten parts cover the service, the component, the service test with TestBed, the component test with overrideProvider, the overrideProvider pattern, the overrideTemplate pattern, the injection token, the inject function in the test, the provider variations, and the TestBed checklist.


Quick Reference

The TestBed Methods

MethodPurpose
configureTestingModuleConfigure the test module
compileComponentsCompile the template
createComponentCreate the component
injectRetrieve a dependency
overrideProviderReplace a provider
overrideComponentOverride a component
overrideTemplateOverride a template
overrideModuleOverride a module
runInInjectionContextRun code in the injection context
resetTestingModuleReset the TestBed

The Provider Variations

VariationPurpose
useValueProvide a static value
useClassProvide a class
useFactoryProvide a factory function
useExistingAlias to an existing provider

The Override Methods

MethodPurpose
overrideProviderReplace a provider
overrideComponentOverride a component
overrideTemplateOverride a template
overrideModuleOverride a module

The Module Configuration

PropertyPurpose
importsStandalone components, modules
declarationsNon-standalone components, directives, pipes
providersServices, tokens
schemasCustom element schemas

Best Practices

✅ Do This:

// Use compileComponents for external templates
await TestBed.configureTestingModule({ imports: [CounterComponent] }).compileComponents(); // ✅
// Use TestBed.inject to retrieve services
service = TestBed.inject(UserService);                           // ✅
// Use overrideProvider to replace dependencies
TestBed.overrideProvider(UserService, { useValue: spy });        // ✅
// Use overrideTemplate to simplify templates
TestBed.overrideTemplate(CounterComponent, '<p>Simple</p>');     // ✅

❌ Don’t Do This:

// Don't forget compileComponents for external templates
TestBed.configureTestingModule({ imports: [CounterComponent] });  // ❌ may fail
// Don't use the real HTTP client in unit tests
providers: [provideHttpClient()]  // without testing                   // ❌
// Don't modify the TestBed after createComponent
fixture = TestBed.createComponent(CounterComponent);
TestBed.overrideProvider(UserService, { useValue: spy });        // ❌ too late
// Don't share state between tests
// Reset in beforeEach.                                          // ❌

Common Pitfalls

PitfallWhy It HappensFix
Component not foundMissing importsAdd to the imports array
Service not foundMissing providersAdd to the providers array
Template not compiledMissing compileComponentsAdd the await compileComponents()
Override not appliedCalled after createComponentCall before createComponent
Token not foundNot providedAdd to the providers array

Real-World Examples

1. Configure Module

TestBed.configureTestingModule({ imports: [CounterComponent] });

2. Compile Components

await TestBed.configureTestingModule({ imports: [CounterComponent] }).compileComponents();

3. Inject Service

service = TestBed.inject(UserService);

4. Override Provider

TestBed.overrideProvider(UserService, { useValue: spy });

5. Override Template

TestBed.overrideTemplate(CounterComponent, '<p>Simple</p>');

6. Injection Token

{ provide: API_URL, useValue: 'https://api.example.com' }

7. Use Value

{ provide: UserService, useValue: mockUserService }

8. Use Class

{ provide: UserService, useClass: MockUserService }

9. Use Factory

{ provide: UserService, useFactory: () => new MockUserService() }

10. Run in Injection Context

TestBed.runInInjectionContext(() => inject(API_URL));

Visual

The TestBed Hierarchy

┌──────────────────────────────────────────────┐
│  TESTBED INJECTOR                            │
│    ├─ imports: components, modules           │
│    ├─ providers: services, tokens            │
│    ├─ declarations: non-standalone           │
│    └─ schemas: custom elements               │
│       │                                      │
│       ▼                                      │
│  COMPONENT INJECTOR                          │
│    └─ child of TestBed injector              │
│       │                                      │
│       ▼                                      │
│  COMPONENT DEPENDENCIES                      │
│    └─ resolved from TestBed or parent        │
│                                              │
└──────────────────────────────────────────────┘

The Override Methods

┌──────────────────────────────────────────────┐
│  OVERRIDE METHODS                            │
│                                              │
│  overrideProvider:                           │
│    └─ Replace a provider with a mock         │
│                                              │
│  overrideComponent:                          │
│    └─ Override a component's configuration   │
│                                              │
│  overrideTemplate:                           │
│    └─ Shorthand for template override        │
│                                              │
│  overrideModule:                             │
│    └─ Override a module's configuration      │
│                                              │
│  Call before createComponent.                │
│                                              │
└──────────────────────────────────────────────┘

The Provider Variations

┌──────────────────────────────────────────────┐
│  PROVIDER VARIATIONS                         │
│                                              │
│  useValue:                                   │
│    └─ A static value                         │
│                                              │
│  useClass:                                   │
│    └─ A class                                │
│                                              │
│  useFactory:                                 │
│    └─ A factory function                     │
│                                              │
│  useExisting:                                │
│    └─ An alias to an existing provider       │
│                                              │
└──────────────────────────────────────────────┘

The Test Lifecycle with TestBed

┌──────────────────────────────────────────────┐
│  TEST LIFECYCLE                              │
│                                              │
│  beforeEach:                                 │
│    ├─ configureTestingModule                 │
│    ├─ compileComponents                      │
│    ├─ overrideProvider (optional)            │
│    ├─ overrideTemplate (optional)            │
│    ├─ createComponent                        │
│    └─ detectChanges                          │
│                                              │
│  it:                                         │
│    ├─ TestBed.inject (optional)              │
│    ├─ interact with the component            │
│    ├─ assert                                 │
│                                              │
│  afterEach:                                  │
│    └─ TestBed resets automatically           │
│                                              │
└──────────────────────────────────────────────┘

Summary

ItemValue
configureTestingModuleConfigure the test module
compileComponentsCompile the template
createComponentCreate the component
injectRetrieve a dependency
overrideProviderReplace a provider
overrideComponentOverride a component
overrideTemplateOverride a template
overrideModuleOverride a module
useValueStatic value provider
useClassClass provider
useFactoryFactory provider
useExistingAlias provider

Key takeaways:

  • TestBed is a dynamic testing module that mirrors the structure of an @NgModule. It has imports, declarations, providers, and schemas. The configureTestingModule method configures the module for the current test .
  • The TestBed.inject method retrieves a dependency from the injector. It is the testing equivalent of the inject function. It works with services, components, directives, pipes, and InjectionToken instances .
  • The overrideProvider method replaces a provider with a mock. It is called after configureTestingModule and before createComponent. The useValue, useClass, useFactory, and useExisting variations provide different kinds of values .
  • The overrideComponent and overrideTemplate methods override a component’s configuration. The overrideTemplate method is a shorthand for replacing the template. The overrideComponent method can also override the providers and the styles .
  • The TestBed is reset between tests automatically. Each test starts with a fresh TestBed. The configureTestingModule method should be called in the beforeEach block so that each test gets a fresh configuration.
  • The provider variations determine what the injector returns. useValue returns a static value. useClass instantiates a class. useFactory calls a factory function. useExisting returns an existing provider’s instance.
  • The runInInjectionContext method runs code in the injection context. It is used when the inject function is called outside a component’s constructor. The TestBed.runInInjectionContext method provides the context.

Remember: TestBed is the dependency injection context for every test. The configureTestingModule method configures the module. The compileComponents method compiles the template. The inject method retrieves a dependency. The overrideProvider method replaces a dependency. The overrideComponent and overrideTemplate methods override the component’s configuration. The TestBed is reset between tests. The tests are the specification of the application’s behavior, and the TestBed is the tool that makes the tests possible.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!