Angular 102 🅰️ Testing — TestBed and Dependency Injection
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
| Method | Purpose |
|---|---|
configureTestingModule | Configure the test module |
compileComponents | Compile the template |
createComponent | Create the component |
inject | Retrieve a dependency |
overrideProvider | Replace a provider |
overrideComponent | Override a component |
overrideTemplate | Override a template |
overrideModule | Override a module |
runInInjectionContext | Run code in the injection context |
resetTestingModule | Reset the TestBed |
The Provider Variations
| Variation | Purpose |
|---|---|
useValue | Provide a static value |
useClass | Provide a class |
useFactory | Provide a factory function |
useExisting | Alias to an existing provider |
The Override Methods
| Method | Purpose |
|---|---|
overrideProvider | Replace a provider |
overrideComponent | Override a component |
overrideTemplate | Override a template |
overrideModule | Override a module |
The Module Configuration
| Property | Purpose |
|---|---|
imports | Standalone components, modules |
declarations | Non-standalone components, directives, pipes |
providers | Services, tokens |
schemas | Custom 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Component not found | Missing imports | Add to the imports array |
| Service not found | Missing providers | Add to the providers array |
| Template not compiled | Missing compileComponents | Add the await compileComponents() |
| Override not applied | Called after createComponent | Call before createComponent |
| Token not found | Not provided | Add 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
| Item | Value |
|---|---|
configureTestingModule | Configure the test module |
compileComponents | Compile the template |
createComponent | Create the component |
inject | Retrieve a dependency |
overrideProvider | Replace a provider |
overrideComponent | Override a component |
overrideTemplate | Override a template |
overrideModule | Override a module |
useValue | Static value provider |
useClass | Class provider |
useFactory | Factory provider |
useExisting | Alias provider |
Key takeaways:
TestBedis a dynamic testing module that mirrors the structure of an@NgModule. It hasimports,declarations,providers, andschemas. TheconfigureTestingModulemethod configures the module for the current test .- The
TestBed.injectmethod retrieves a dependency from the injector. It is the testing equivalent of theinjectfunction. It works with services, components, directives, pipes, andInjectionTokeninstances . - The
overrideProvidermethod replaces a provider with a mock. It is called afterconfigureTestingModuleand beforecreateComponent. TheuseValue,useClass,useFactory, anduseExistingvariations provide different kinds of values . - The
overrideComponentandoverrideTemplatemethods override a component’s configuration. TheoverrideTemplatemethod is a shorthand for replacing the template. TheoverrideComponentmethod can also override the providers and the styles . - The
TestBedis reset between tests automatically. Each test starts with a freshTestBed. TheconfigureTestingModulemethod should be called in thebeforeEachblock so that each test gets a fresh configuration. - The provider variations determine what the injector returns.
useValuereturns a static value.useClassinstantiates a class.useFactorycalls a factory function.useExistingreturns an existing provider’s instance. - The
runInInjectionContextmethod runs code in the injection context. It is used when theinjectfunction is called outside a component’s constructor. TheTestBed.runInInjectionContextmethod 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.
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!