🧪 Angular Advanced Testing: Your Secret Superpower
Imagine you’re a detective with a magnifying glass, checking every corner of your app to make sure everything works perfectly. That’s what testing is!
🎭 The Story: Meet Detective Angular
Once upon a time, there was a brilliant detective named Angular. Detective Angular had built an amazing city (your app), but needed to make sure every building, every road, and every traffic light worked perfectly.
To do this, Detective Angular used seven magical tools. Let’s discover each one!
🔮 Tool 1: Async Testing Utilities
What’s the Problem?
Sometimes your app does things that take time—like fetching data from the internet. It’s like ordering pizza: you don’t get it instantly!
The Magic Solution
Angular gives you special helpers to wait for these slow things:
// fakeAsync: Pretend time machine!
it('should load data', fakeAsync(() => {
let result = '';
fetchData().then(data => {
result = data;
});
// Fast-forward time!
tick(1000);
expect(result).toBe('Hello!');
}));
The Three Magic Words
| Helper | What It Does |
|---|---|
fakeAsync |
Creates a fake time zone |
tick() |
Fast-forwards time |
flush() |
Finishes ALL waiting tasks |
graph TD A["Start Test"] --> B["fakeAsync Zone"] B --> C["Do Async Stuff"] C --> D["tick - Skip Time"] D --> E["Check Results"] E --> F["✅ Test Complete"]
Real-World Example
it('shows loading then data',
fakeAsync(() => {
component.loadUsers();
// Still loading...
expect(component.loading)
.toBe(true);
// Skip 2 seconds
tick(2000);
// Now loaded!
expect(component.users.length)
.toBeGreaterThan(0);
}));
🌐 Tool 2: HTTP Testing
The Story
Imagine your app needs to call a restaurant (server) to get food (data). But during testing, we don’t want to call the REAL restaurant. What if it’s closed? What if it’s slow?
The Solution: Fake Restaurant!
// Setup the fake restaurant
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [MyService]
});
httpMock = TestBed.inject(
HttpTestingController
);
});
Making Fake Orders
it('gets users from API', () => {
const fakeUsers = [
{ id: 1, name: 'Ana' },
{ id: 2, name: 'Bob' }
];
// Make the request
service.getUsers().subscribe(users => {
expect(users.length).toBe(2);
});
// Catch the request
const req = httpMock.expectOne(
'/api/users'
);
// Send fake response
req.flush(fakeUsers);
});
Testing Errors Too!
it('handles server error', () => {
service.getUsers().subscribe({
error: (err) => {
expect(err.status).toBe(500);
}
});
const req = httpMock.expectOne(
'/api/users'
);
// Simulate error!
req.flush('Oops!', {
status: 500,
statusText: 'Server Error'
});
});
graph TD A["Test Starts"] --> B["Service Makes Request"] B --> C["httpMock Catches It"] C --> D["You Send Fake Response"] D --> E["Service Gets Fake Data"] E --> F["✅ Test Passes"]
🧭 Tool 3: Router Testing
The Challenge
Your app has many pages (routes). How do you test if clicking a button takes you to the right page?
The RouterTestingModule
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{ path: 'home', component: Home },
{ path: 'about', component: About }
])
]
});
});
Testing Navigation
it('navigates to about page',
fakeAsync(() => {
const router = TestBed.inject(Router);
const location = TestBed.inject(Location);
// Create component
const fixture = TestBed
.createComponent(AppComponent);
// Initialize router
router.initialNavigation();
// Navigate!
router.navigate(['about']);
tick();
// Check location
expect(location.path())
.toBe('/about');
}));
Testing Route Guards
it('blocks unauthorized access',
fakeAsync(() => {
// User not logged in
authService.isLoggedIn = false;
router.navigate(['admin']);
tick();
// Should redirect to login
expect(location.path())
.toBe('/login');
}));
🎭 Tool 4: Mock Patterns
What’s a Mock?
A mock is like a stunt double in movies. It looks like the real thing but is safer and easier to control!
The Spy Pattern
// Create a spy on a method
spyOn(userService, 'getUser')
.and.returnValue(of({
id: 1,
name: 'Test User'
}));
// Now getUser() returns
// our fake data!
The Stub Pattern
// Full fake service
const mockUserService = {
getUser: () => of({
id: 1,
name: 'Fake'
}),
saveUser: () => of(true)
};
TestBed.configureTestingModule({
providers: [
{
provide: UserService,
useValue: mockUserService
}
]
});
The Mock Class Pattern
class MockUserService {
users = [{ id: 1, name: 'Ana' }];
getUser(id: number) {
return of(
this.users.find(u => u.id === id)
);
}
saveUser(user: any) {
this.users.push(user);
return of(true);
}
}
graph TD A["Real Service"] --> B{Testing?} B -->|Yes| C["Use Mock"] B -->|No| D["Use Real"] C --> E["Fast & Predictable"] D --> F["Slow & Unpredictable"]
🎮 Tool 5: Component Harnesses
The Problem with DOM Testing
Finding buttons and inputs in HTML is messy:
// Old way - fragile! 😰
const button = fixture.nativeElement
.querySelector('button.submit-btn');
If someone changes the class name, your test breaks!
The Harness Solution
// Setup harness loader
let loader: HarnessLoader;
beforeEach(() => {
fixture = TestBed.createComponent(App);
loader = TestbedHarnessEnvironment
.loader(fixture);
});
Using Built-in Harnesses
it('clicks mat-button', async () => {
// Find button by harness
const button = await loader
.getHarness(MatButtonHarness);
// Click it!
await button.click();
// Check result
expect(component.clicked)
.toBe(true);
});
Finding Specific Elements
it('fills form', async () => {
// Find input by label
const input = await loader.getHarness(
MatInputHarness.with({
placeholder: 'Enter name'
})
);
// Type in it!
await input.setValue('Ana');
expect(component.name).toBe('Ana');
});
🏃 Tool 6: Shallow Testing
Deep vs Shallow
| Deep Testing | Shallow Testing |
|---|---|
| Renders child components | Ignores children |
| Slow | Fast |
| Tests integration | Tests isolation |
The CUSTOM_ELEMENTS_SCHEMA Trick
TestBed.configureTestingModule({
declarations: [ParentComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
This tells Angular: “Don’t worry about unknown tags!”
The NO_ERRORS_SCHEMA Option
TestBed.configureTestingModule({
declarations: [MyComponent],
schemas: [NO_ERRORS_SCHEMA]
});
Even more relaxed—ignores ALL unknown things.
When to Use Shallow Testing
graph TD A["What to Test?"] --> B{Just This Component?} B -->|Yes| C["Shallow Test ⚡"] B -->|No| D{With Children?} D -->|Yes| E["Deep Test 🐢"] D -->|No| F["Unit Test 🎯"]
Example
@Component({
template: `
<h1>{{ title }}</h1>
<app-child></app-child>
<app-footer></app-footer>
`
})
class ParentComponent {
title = 'Hello';
}
// Shallow test - doesn't need
// Child or Footer components!
it('shows title', () => {
const h1 = fixture.nativeElement
.querySelector('h1');
expect(h1.textContent).toBe('Hello');
});
🔧 Tool 7: Test Provider Overrides
The Power of Overrides
Sometimes you need to swap out a service JUST for one test. Here’s how!
overrideComponent
TestBed.overrideComponent(
MyComponent,
{
set: {
providers: [
{
provide: DataService,
useClass: MockDataService
}
]
}
}
);
overrideModule
TestBed.overrideModule(
SharedModule,
{
remove: {
exports: [HeavyComponent]
},
add: {
exports: [LightComponent]
}
}
);
overrideProvider
TestBed.overrideProvider(
API_URL,
{ useValue: 'http://test.local' }
);
Real-World Example
describe('UserProfile', () => {
it('shows offline message', () => {
// Override just for this test!
TestBed.overrideProvider(
ConnectionService,
{
useValue: {
isOnline: () => false
}
}
);
fixture = TestBed.createComponent(
UserProfile
);
expect(fixture.nativeElement
.textContent)
.toContain('You are offline');
});
});
🎯 Quick Summary
| Tool | Purpose | Key Method |
|---|---|---|
| Async Utils | Test slow stuff | fakeAsync, tick |
| HTTP Testing | Fake server calls | HttpTestingController |
| Router Testing | Test navigation | RouterTestingModule |
| Mocks | Fake dependencies | spyOn, useValue |
| Harnesses | Easy DOM access | loader.getHarness() |
| Shallow Testing | Fast unit tests | CUSTOM_ELEMENTS_SCHEMA |
| Provider Overrides | Swap services | overrideProvider |
🚀 You’re Now a Testing Hero!
graph TD A["🎯 You"] --> B["Know Async Testing"] A --> C["Master HTTP Mocks"] A --> D["Navigate Router Tests"] A --> E["Create Perfect Mocks"] A --> F["Use Harnesses"] A --> G["Write Fast Shallow Tests"] A --> H["Override Providers"] B & C & D & E & F & G & H --> I["🏆 Testing Champion!"]
Remember: Every great app has great tests! Now go make your code bulletproof! 💪
