Testing Angular Applications
Angular uses Jasmine as the testing framework and Karma as the test runner. Testing is built into the Angular CLI from the start.
Unit Testing Components
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CounterComponent } from './counter.component';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
describe('CounterComponent', () => {
let component: CounterComponent;
let fixture: ComponentFixture<CounterComponent>;
let compiled: HTMLElement;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CounterComponent]
}).compileComponents();
fixture = TestBed.createComponent(CounterComponent);
component = fixture.componentInstance;
compiled = fixture.nativeElement;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should display initial count', () => {
const countElement = compiled.querySelector('.count');
expect(countElement?.textContent).toContain('0');
});
it('should increment count when button clicked', () => {
const button = compiled.querySelector('.increment-btn') as HTMLButtonElement;
button.click();
fixture.detectChanges();
expect(component.count).toBe(1);
expect(compiled.querySelector('.count')?.textContent).toContain('1');
});
it('should decrement count', () => {
component.count = 5;
fixture.detectChanges();
component.decrement();
fixture.detectChanges();
expect(component.count).toBe(4);
});
});Testing Services
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();
});
it('should fetch users', () => {
const mockUsers = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
];
service.getUsers().subscribe(users => {
expect(users.length).toBe(2);
expect(users).toEqual(mockUsers);
});
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
req.flush(mockUsers);
});
it('should handle error', () => {
service.getUsers().subscribe(
() => fail('should have failed'),
(error) => {
expect(error.status).toBe(404);
}
);
const req = httpMock.expectOne('/api/users');
req.flush('Not found', { status: 404, statusText: 'Not Found' });
});
});Testing with Dependencies
describe('UserListComponent', () => {
let component: UserListComponent;
let fixture: ComponentFixture<UserListComponent>;
let userService: jasmine.SpyObj<UserService>;
beforeEach(async () => {
// Create spy
const userServiceSpy = jasmine.createSpyObj('UserService', ['getUsers']);
await TestBed.configureTestingModule({
declarations: [UserListComponent],
providers: [
{ provide: UserService, useValue: userServiceSpy }
]
}).compileComponents();
fixture = TestBed.createComponent(UserListComponent);
component = fixture.componentInstance;
userService = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
});
it('should load users on init', () => {
const mockUsers = [{ id: 1, name: 'John' }];
userService.getUsers.and.returnValue(of(mockUsers));
component.ngOnInit();
expect(userService.getUsers).toHaveBeenCalled();
expect(component.users).toEqual(mockUsers);
});
});Testing Directives
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
@Input() appHighlight = '';
constructor(private el: ElementRef) { }
@HostListener('mouseenter') onMouseEnter() {
this.highlight(this.appHighlight || 'yellow');
}
@HostListener('mouseleave') onMouseLeave() {
this.highlight('');
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
describe('HighlightDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let des: DebugElement[];
@Component({
template: `
<p appHighlight>Default</p>
<p appHighlight="red">Red</p>
`
})
class TestComponent { }
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [HighlightDirective, TestComponent]
}).createComponent(TestComponent);
fixture.detectChanges();
des = fixture.debugElement.queryAll(By.directive(HighlightDirective));
});
it('should have 2 highlighted elements', () => {
expect(des.length).toBe(2);
});
it('should color first element yellow on mouseenter', () => {
des[0].triggerEventHandler('mouseenter', null);
expect(des[0].nativeElement.style.backgroundColor).toBe('yellow');
});
});Testing Async Operations
import { fakeAsync, tick, flush, waitForAsync } from '@angular/core/testing';
describe('Async Tests', () => {
// fakeAsync - control time
it('should work with fakeAsync', fakeAsync(() => {
let value = false;
setTimeout(() => {
value = true;
}, 1000);
expect(value).toBe(false);
tick(1000); // Advance time by 1000ms
expect(value).toBe(true);
}));
// waitForAsync - wait for promises
it('should work with async', waitForAsync(() => {
const promise = new Promise(resolve => {
setTimeout(() => resolve('done'), 1000);
});
promise.then(value => {
expect(value).toBe('done');
});
}));
// Test observables
it('should handle observables', fakeAsync(() => {
let result: string;
of('test').pipe(delay(1000)).subscribe(value => {
result = value;
});
tick(1000);
expect(result!).toBe('test');
}));
});Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free