Angular Testing Explained — Unit Tests and Integration Tests
In this tutorial, you will learn about Angular Testing Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
Angular testing uses Jasmine and TestBed to verify components, services, directives, pipes, and HTTP interactions work correctly through unit and integration tests.
What You'll Learn
- How to set up TestBed for component testing
- How to test components with inputs, outputs, and dependencies
- How to test services with HTTP mocking
- How to test directives and pipes
- Best practices for Angular testing
Why It Matters
Automated tests catch regressions early, document how components work, and give you confidence to refactor. Without tests, a change in one component can silently break another.
Real-World Use
Durga Antivirus Pro runs over 2000 tests on every pull request, covering threat detection services, dashboard components, and form validations. Tests run in under 30 seconds and catch 95% of regressions before deployment.
flowchart LR
A[Developer] --> B[ng test]
B --> C[Karma / Jest]
C --> D[TestBed]
D --> E[Component]
D --> F[Service]
D --> G[Pipe]
C --> H[Jasmine Runner]
H --> I[Results]
style A fill:#f97316,color:#fff
Testing a Component
Set up a component with TestBed:
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { GreetingComponent } from "./greeting.component";
describe("GreetingComponent", () => {
let component: GreetingComponent;
let fixture: ComponentFixture<GreetingComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GreetingComponent] // standalone component imports itself
}).compileComponents();
fixture = TestBed.createComponent(GreetingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
it("should display the name", () => {
component.name = "Alice";
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector("h1")?.textContent).toContain("Alice");
});
it("should emit greet event on button click", () => {
spyOn(component.greet, "emit");
const button = fixture.nativeElement.querySelector("button");
button?.click();
expect(component.greet.emit).toHaveBeenCalledWith("Alice");
});
});
Expected output: Three tests pass: component creation, name display, and event emission.
TestBed.configureTestingModule sets up the testing module. createComponent creates the component instance. detectChanges triggers change detection. The component's DOM is available through fixture.nativeElement.
Testing with Mock Dependencies
Mock services and router dependencies:
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { UserProfileComponent } from "./user-profile.component";
import { UserService } from "./user.service";
import { of } from "rxjs";
describe("UserProfileComponent", () => {
let component: UserProfileComponent;
let fixture: ComponentFixture<UserProfileComponent>;
let mockUserService: jasmine.SpyObj<UserService>;
const mockUser = { id: 1, name: "John Doe", email: "john@example.com" };
beforeEach(async () => {
mockUserService = jasmine.createSpyObj("UserService", ["getUserById"]);
mockUserService.getUserById.and.returnValue(of(mockUser));
await TestBed.configureTestingModule({
imports: [UserProfileComponent],
providers: [
{ provide: UserService, useValue: mockUserService }
]
}).compileComponents();
fixture = TestBed.createComponent(UserProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should load user data on init", () => {
expect(component.user?.name).toBe("John Doe");
});
it("should display user name in template", () => {
const nameEl = fixture.nativeElement.querySelector(".user-name");
expect(nameEl?.textContent).toContain("John Doe");
});
});
Expected output: The component loads and displays the mock user without making a real HTTP call.
jasmine.createSpyObj creates a mock object with spy methods. and.returnValue sets the return value. Providing the mock in providers replaces the real service in the test.
Testing a Service with HTTP
Use HttpTestingController to mock HTTP calls:
import { TestBed } from "@angular/core/testing";
import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { UserService } from "./user.service";
describe("UserService", () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify(); // Ensure no outstanding requests
});
it("should fetch users", () => {
const mockUsers = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
service.getUsers().subscribe(users => {
expect(users.length).toBe(2);
expect(users).toEqual(mockUsers);
});
const req = httpMock.expectOne("https://jsonplaceholder.typicode.com/users");
expect(req.request.method).toBe("GET");
req.flush(mockUsers);
});
it("should handle HTTP errors", () => {
service.getUsers().subscribe({
error: (error) => {
expect(error.status).toBe(500);
}
});
const req = httpMock.expectOne("https://jsonplaceholder.typicode.com/users");
req.flush("Server Error", { status: 500, statusText: "Internal Server Error" });
});
});
Expected output: The service test verifies GET requests and error handling without a real server.
HttpTestingController intercepts all HTTP requests. expectOne asserts that exactly one request to the given URL was made. flush provides the mock response. verify ensures no unmatched requests remain.
Testing a Directive
Test directives with a host component:
import { Component } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { HighlightDirective } from "./highlight.directive";
@Component({
standalone: true,
imports: [HighlightDirective],
template: `<p [appHighlight]="'red'">Test text</p>`
})
class TestHostComponent {}
describe("HighlightDirective", () => {
let fixture: ComponentFixture<TestHostComponent>;
let pElement: HTMLElement;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestHostComponent]
}).compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
pElement = fixture.nativeElement.querySelector("p")!;
});
it("should highlight on mouse enter", () => {
pElement.dispatchEvent(new Event("mouseenter"));
expect(pElement.style.backgroundColor).toBe("red");
});
it("should remove highlight on mouse leave", () => {
pElement.dispatchEvent(new Event("mouseenter"));
pElement.dispatchEvent(new Event("mouseleave"));
expect(pElement.style.backgroundColor).toBe("transparent");
});
});
Expected output: The directive properly sets and removes background color on hover events.
Testing directives requires a host component that uses the directive. You can then interact with the DOM and verify the directive's behavior through native events.
Testing a Pipe
Pipes are pure functions and easy to test:
import { TruncatePipe } from "./truncate.pipe";
describe("TruncatePipe", () => {
let pipe: TruncatePipe;
beforeEach(() => {
pipe = new TruncatePipe();
});
it("should return the same string if shorter than max length", () => {
expect(pipe.transform("Hello")).toBe("Hello");
});
it("should truncate and add suffix", () => {
expect(pipe.transform("Hello World Long Text", 5)).toBe("Hello...");
});
it("should use custom suffix", () => {
expect(pipe.transform("Hello World", 5, "---")).toBe("Hello---");
});
it("should handle empty string", () => {
expect(pipe.transform("")).toBe("");
});
it("should handle null value", () => {
expect(pipe.transform(null as any)).toBe("");
});
});
Expected output: All pipe tests pass, covering normal usage, edge cases, and boundary conditions.
Pipes are the simplest Angular construct to test because they are pure functions with no dependencies. Test the transform method directly with various inputs.
Common Mistakes
Not calling detectChanges — Without
detectChanges, the template does not update. Tests pass against outdated values.Testing implementation details — Test the rendered DOM and emitted events, not internal component state. Tests that check private methods break on Refactoring.
Not cleaning up HTTP mock — Unmatched requests from one test can make another test fail. Always call
httpMock.verify()inafterEach.Async tests without fakeAsync — HTTP calls and timers need
fakeAsyncorasynchelpers for proper timing control.Too much setup in beforeEach — Complex setups make tests hard to read. Extract shared setup into helper functions.
Practice Questions
What is TestBed in Angular testing? A testing utility that configures and creates Angular modules for Integration Testing of components and services.
How do you mock an HTTP service? Use
HttpClientTestingModuleandHttpTestingControllerto intercept and respond to HTTP requests.What does
fixture.detectChanges()do? It triggers Angular's change detection, updating the template with current component state.How do you test a component's event emission? Spy on the
Outputproperty withspyOn(component.eventName, 'emit')and trigger the event.What is the purpose of async in TestBed?
compileComponents()is async because it compiles external templates and styles. Useawaitorasync.
Challenge
Write a complete test suite for a LoginFormComponent with email and password fields. Test: form renders with all fields, valid email shows no error, invalid email shows error, submit button is disabled when form is invalid, submit emits login event with correct data, and loading state disables the form button.
FAQ
Mini Project
Write a comprehensive test suite for a TaskManagerComponent and its dependencies. Create: unit tests for the TaskService (HTTP mock with CRUD operations), component tests for TaskListComponent (renders tasks, marks complete, deletes), integration tests for TaskFormComponent (validation, submission), and a pipe test for the TaskFilterPipe. Achieve 100% coverage for the service and pipe, 80%+ for components.
What's Next
Continue with Internationalization and NgRx:
Angular i18n, Angular State Management, Angular Forms Validation
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro