Angular Common provides comprehensive testing utilities including mock implementations for location services, HTTP client testing infrastructure, and testing helpers for unit and integration testing of Angular applications.
Mock implementation of Location service for testing navigation and URL manipulation.
/**
* Spy implementation of Location service for testing
* Tracks navigation events and allows simulation of browser behavior
*/
export class SpyLocation implements Location {
/** Array of URL changes that occurred during testing */
urlChanges: string[];
path(includeHash?: boolean): string;
isCurrentPathEqualTo(path: string, query?: string): boolean;
normalize(url: string): string;
prepareExternalUrl(url: string): string;
go(path: string, query?: string, state?: any): void;
replaceState(path: string, query?: string, state?: any): void;
forward(): void;
back(): void;
historyGo(relativePosition?: number): void;
onUrlChange(fn: (url: string, state: unknown) => void): VoidFunction;
subscribe(onNext: (value: PopStateEvent) => void, onThrow?: ((exception: any) => void) | null, onReturn?: (() => void) | null): SubscriptionLike;
/** Simulate popstate event */
simulateUrlPop(pathname: string): void;
/** Simulate hash change event */
simulateHashChange(pathname: string): void;
/** Set current URL for testing */
setInitialPath(url: string): void;
/** Get history of URL changes */
getUrlChanges(): string[];
}Mock implementation of LocationStrategy for testing different routing strategies.
/**
* Mock implementation of LocationStrategy for testing
* Provides controllable location strategy behavior
*/
export class MockLocationStrategy extends LocationStrategy {
/** Internal base href */
internalBaseHref: string;
/** Internal path */
internalPath: string;
/** Internal title */
internalTitle: string;
/** Array of URL changes */
urlChanges: string[];
path(includeHash?: boolean): string;
prepareExternalUrl(internal: string): string;
getState(): unknown;
pushState(state: any, title: string, url: string, queryParams: string): void;
replaceState(state: any, title: string, url: string, queryParams: string): void;
forward(): void;
back(): void;
onPopState(fn: LocationChangeListener): void;
getBaseHref(): string;
/** Simulate popstate event with URL */
simulatePopState(url: string): void;
}Mock implementation of PlatformLocation for testing platform-specific behavior.
/**
* Mock implementation of PlatformLocation for testing
* Provides controllable platform location behavior
*/
export class MockPlatformLocation extends PlatformLocation {
/** Configuration for mock behavior */
constructor(config?: MockPlatformLocationConfig);
getBaseHref(): string;
getState(): unknown;
onPopState(fn: LocationChangeListener): void;
onHashChange(fn: LocationChangeListener): void;
get pathname(): string;
get search(): string;
get hash(): string;
pushState(state: any, title: string, url: string): void;
replaceState(state: any, title: string, url: string): void;
forward(): void;
back(): void;
historyGo(relativePosition: number): void;
get hostname(): string;
get protocol(): string;
get port(): string;
get href(): string;
}
/** Configuration interface for MockPlatformLocation */
export interface MockPlatformLocationConfig {
startUrl?: string;
appBaseHref?: string;
}
/** Injection token for MockPlatformLocation configuration */
export const MOCK_PLATFORM_LOCATION_CONFIG: InjectionToken<MockPlatformLocationConfig>;Convenience function for setting up location testing.
/**
* Provides location testing utilities
* Sets up mock location services for testing
*/
export function provideLocationMocks(): Provider[];Usage Examples:
// Basic location testing setup
describe('NavigationService', () => {
let service: NavigationService;
let location: SpyLocation;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
NavigationService,
{ provide: Location, useClass: SpyLocation }
]
});
service = TestBed.inject(NavigationService);
location = TestBed.inject(Location) as SpyLocation;
});
it('should navigate to user page', () => {
service.navigateToUser(123);
expect(location.path()).toBe('/users/123');
expect(location.urlChanges).toContain('/users/123');
});
it('should navigate with query parameters', () => {
service.navigateToUserWithQuery(123, 'profile');
expect(location.path()).toBe('/users/123?tab=profile');
});
it('should handle back navigation', fakeAsync(() => {
service.navigateToUser(123);
service.navigateToUser(456);
service.goBack();
tick();
expect(location.path()).toBe('/users/123');
}));
it('should simulate URL changes', () => {
const urlChanges: string[] = [];
location.onUrlChange((url) => urlChanges.push(url));
location.simulateUrlPop('/new-url');
expect(urlChanges).toContain('/new-url');
});
});
// Testing with MockLocationStrategy
describe('RoutingComponent', () => {
let component: RoutingComponent;
let fixture: ComponentFixture<RoutingComponent>;
let locationStrategy: MockLocationStrategy;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [RoutingComponent],
providers: [
{ provide: LocationStrategy, useClass: MockLocationStrategy }
]
});
fixture = TestBed.createComponent(RoutingComponent);
component = fixture.componentInstance;
locationStrategy = TestBed.inject(LocationStrategy) as MockLocationStrategy;
});
it('should handle popstate events', () => {
spyOn(component, 'onPopState');
locationStrategy.simulatePopState('/test-url');
expect(component.onPopState).toHaveBeenCalled();
});
it('should track URL changes', () => {
locationStrategy.pushState(null, '', '/page1', '');
locationStrategy.pushState(null, '', '/page2', '');
expect(locationStrategy.urlChanges).toEqual(['/page1', '/page2']);
});
});
// Testing with MockPlatformLocation
describe('PlatformService', () => {
let service: PlatformService;
let platformLocation: MockPlatformLocation;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
PlatformService,
{
provide: PlatformLocation,
useFactory: () => new MockPlatformLocation({
startUrl: 'http://localhost:4200/test',
appBaseHref: '/app/'
})
}
]
});
service = TestBed.inject(PlatformService);
platformLocation = TestBed.inject(PlatformLocation) as MockPlatformLocation;
});
it('should provide mock URL components', () => {
expect(platformLocation.pathname).toBe('/test');
expect(platformLocation.hostname).toBe('localhost');
expect(platformLocation.port).toBe('4200');
expect(platformLocation.getBaseHref()).toBe('/app/');
});
it('should simulate platform navigation', () => {
platformLocation.pushState(null, '', '/new-page');
expect(platformLocation.pathname).toBe('/new-page');
});
});
// Using provideLocationMocks
describe('IntegrationTest', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
...provideLocationMocks(),
// Other providers
]
});
});
it('should have all location mocks configured', () => {
const location = TestBed.inject(Location);
const locationStrategy = TestBed.inject(LocationStrategy);
const platformLocation = TestBed.inject(PlatformLocation);
expect(location).toBeInstanceOf(SpyLocation);
expect(locationStrategy).toBeInstanceOf(MockLocationStrategy);
expect(platformLocation).toBeInstanceOf(MockPlatformLocation);
});
});Service for controlling and verifying HTTP requests in tests.
/**
* Controller for testing HTTP requests
* Allows verification and mocking of HTTP calls
*/
export abstract class HttpTestingController {
/** Expect a single request matching criteria */
abstract expectOne(url: string, description?: string): TestRequest;
abstract expectOne(params: RequestMatch, description?: string): TestRequest;
abstract expectOne(matchFn: ((req: HttpRequest<any>) => boolean), description?: string): TestRequest;
/** Expect no requests matching criteria */
abstract expectNone(url: string, description?: string): void;
abstract expectNone(params: RequestMatch, description?: string): void;
abstract expectNone(matchFn: ((req: HttpRequest<any>) => boolean), description?: string): void;
/** Find all requests matching criteria */
abstract match(url: string): TestRequest[];
abstract match(params: RequestMatch): TestRequest[];
abstract match(matchFn: ((req: HttpRequest<any>) => boolean)): TestRequest[];
/** Verify no outstanding requests */
abstract verify(opts?: { ignoreCancelled?: boolean }): void;
}
/** Interface for request matching criteria */
export interface RequestMatch {
method?: string;
url?: string;
}Represents a single HTTP request in tests.
/**
* Individual HTTP request for testing
* Allows response simulation and request inspection
*/
export class TestRequest {
/** The HTTP request */
readonly request: HttpRequest<any>;
/** Whether request was cancelled */
readonly cancelled: boolean;
/** Respond with successful data */
flush(body: ArrayBuffer | Blob | boolean | string | number | Object | (boolean | string | number | Object)[] | null, opts?: TestResponseOptions): void;
/** Respond with error */
error(error: ErrorEvent, opts?: TestResponseOptions): void;
/** Respond with specific event */
event(event: HttpEvent<any>): void;
}
/** Options for test responses */
interface TestResponseOptions {
headers?: HttpHeaders | { [name: string]: string | string[] };
status?: number;
statusText?: string;
}Module for setting up HTTP testing infrastructure.
/**
* Module for HTTP client testing
* Provides HttpTestingController and test backend
*/
export class HttpClientTestingModule {
static forRoot(): ModuleWithProviders<HttpClientTestingModule>;
}
/**
* Provider function for HTTP client testing
* Modern alternative to HttpClientTestingModule
*/
export function provideHttpClientTesting(): Provider[];Usage Examples:
// Basic HTTP testing setup
describe('UserService', () => {
let service: UserService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule], // Or use providers: [provideHttpClient(), provideHttpClientTesting()]
providers: [UserService]
});
service = TestBed.inject(UserService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify(); // Verify no outstanding requests
});
it('should fetch users', () => {
const mockUsers = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
];
service.getUsers().subscribe(users => {
expect(users).toEqual(mockUsers);
});
// Expect one request
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('GET');
// Respond with mock data
req.flush(mockUsers);
});
it('should create user', () => {
const newUser = { name: 'Bob', email: 'bob@example.com' };
const createdUser = { id: 3, ...newUser };
service.createUser(newUser).subscribe(user => {
expect(user).toEqual(createdUser);
});
const req = httpMock.expectOne('/api/users');
expect(req.request.method).toBe('POST');
expect(req.request.body).toEqual(newUser);
req.flush(createdUser, { status: 201, statusText: 'Created' });
});
it('should handle errors', () => {
service.getUser(999).subscribe({
next: () => fail('Should have failed'),
error: (error) => {
expect(error.status).toBe(404);
expect(error.statusText).toBe('Not Found');
}
});
const req = httpMock.expectOne('/api/users/999');
req.error(new ErrorEvent('Network error'), {
status: 404,
statusText: 'Not Found'
});
});
it('should handle request headers', () => {
service.getProtectedData().subscribe();
const req = httpMock.expectOne('/api/protected');
expect(req.request.headers.get('Authorization')).toContain('Bearer');
req.flush({ data: 'secret' });
});
it('should handle query parameters', () => {
service.searchUsers('john', 10).subscribe();
const req = httpMock.expectOne(req =>
req.url === '/api/users/search' &&
req.params.get('q') === 'john' &&
req.params.get('limit') === '10'
);
req.flush([]);
});
it('should handle multiple requests', () => {
// Make multiple requests
service.getUser(1).subscribe();
service.getUser(2).subscribe();
service.getUsers().subscribe();
// Handle them individually
const userReq1 = httpMock.expectOne('/api/users/1');
const userReq2 = httpMock.expectOne('/api/users/2');
const usersReq = httpMock.expectOne('/api/users');
userReq1.flush({ id: 1, name: 'John' });
userReq2.flush({ id: 2, name: 'Jane' });
usersReq.flush([]);
});
it('should verify no unexpected requests', () => {
service.getUsers().subscribe();
const req = httpMock.expectOne('/api/users');
req.flush([]);
// This will pass because we handled all requests
httpMock.verify();
});
});
// Testing with request matching
describe('ApiService Advanced', () => {
let service: ApiService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideHttpClient(),
provideHttpClientTesting(),
ApiService
]
});
service = TestBed.inject(ApiService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('should match requests by method and URL', () => {
service.updateUser(1, { name: 'Updated' }).subscribe();
const req = httpMock.expectOne({ method: 'PUT', url: '/api/users/1' });
req.flush({ id: 1, name: 'Updated' });
});
it('should match requests with custom function', () => {
service.uploadFile(new File(['content'], 'test.txt')).subscribe();
const req = httpMock.expectOne(req =>
req.method === 'POST' &&
req.url === '/api/upload' &&
req.body instanceof FormData
);
req.flush({ fileId: 'abc123' });
});
it('should find multiple matching requests', () => {
// Make multiple requests to same endpoint
service.getUser(1).subscribe();
service.getUser(2).subscribe();
service.getUser(3).subscribe();
// Find all requests to users endpoint
const requests = httpMock.match(req => req.url.startsWith('/api/users/'));
expect(requests.length).toBe(3);
// Respond to all
requests.forEach((req, index) => {
req.flush({ id: index + 1, name: `User ${index + 1}` });
});
});
it('should expect no requests', () => {
// This should not make any HTTP requests
service.getCachedData().subscribe();
// Verify no requests were made
httpMock.expectNone('/api/data');
});
it('should handle request progress events', () => {
let progressEvents: any[] = [];
service.uploadLargeFile(new File(['data'], 'large.dat')).subscribe(event => {
progressEvents.push(event);
});
const req = httpMock.expectOne('/api/upload');
// Simulate progress events
req.event({ type: HttpEventType.Sent } as HttpSentEvent);
req.event({
type: HttpEventType.UploadProgress,
loaded: 50,
total: 100
} as HttpUploadProgressEvent);
req.event({
type: HttpEventType.Response,
body: { fileId: 'xyz789' }
} as HttpResponse<any>);
expect(progressEvents.length).toBe(3);
expect(progressEvents[1].loaded).toBe(50);
});
it('should handle cancelled requests', () => {
const subscription = service.getLongRunningData().subscribe();
subscription.unsubscribe(); // Cancel the request
const req = httpMock.expectOne('/api/long-running');
expect(req.cancelled).toBe(true);
// Don't need to flush cancelled requests
});
});
// Testing interceptors
describe('AuthInterceptor', () => {
let httpMock: HttpTestingController;
let http: HttpClient;
let authService: jasmine.SpyObj<AuthService>;
beforeEach(() => {
const authSpy = jasmine.createSpyObj('AuthService', ['getToken']);
TestBed.configureTestingModule({
providers: [
provideHttpClient(withInterceptors([authInterceptor])),
provideHttpClientTesting(),
{ provide: AuthService, useValue: authSpy }
]
});
httpMock = TestBed.inject(HttpTestingController);
http = TestBed.inject(HttpClient);
authService = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>;
});
afterEach(() => {
httpMock.verify();
});
it('should add auth token to requests', () => {
authService.getToken.and.returnValue('test-token');
http.get('/api/protected').subscribe();
const req = httpMock.expectOne('/api/protected');
expect(req.request.headers.get('Authorization')).toBe('Bearer test-token');
req.flush({ data: 'protected' });
});
it('should not add token when not available', () => {
authService.getToken.and.returnValue(null);
http.get('/api/public').subscribe();
const req = httpMock.expectOne('/api/public');
expect(req.request.headers.has('Authorization')).toBe(false);
req.flush({ data: 'public' });
});
});Examples of testing complete Angular applications with location and HTTP mocking.
Usage Examples:
// Full integration test setup
describe('AppComponent Integration', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let httpMock: HttpTestingController;
let location: SpyLocation;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [RouterTestingModule],
providers: [
provideHttpClient(),
provideHttpClientTesting(),
...provideLocationMocks()
]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
httpMock = TestBed.inject(HttpTestingController);
location = TestBed.inject(Location) as SpyLocation;
});
afterEach(() => {
httpMock.verify();
});
it('should navigate and load data', fakeAsync(() => {
// Initial navigation
location.go('/users');
fixture.detectChanges();
tick();
// Should trigger HTTP request
const req = httpMock.expectOne('/api/users');
req.flush([{ id: 1, name: 'John' }]);
tick();
fixture.detectChanges();
// Verify UI updates
expect(fixture.debugElement.nativeElement.textContent).toContain('John');
expect(location.path()).toBe('/users');
}));
it('should handle navigation errors', fakeAsync(() => {
location.go('/users/999');
fixture.detectChanges();
tick();
const req = httpMock.expectOne('/api/users/999');
req.error(new ErrorEvent('Not Found'), { status: 404 });
tick();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement.textContent).toContain('User not found');
}));
});
// Testing with real routing
describe('UserComponent Routing', () => {
let component: UserComponent;
let fixture: ComponentFixture<UserComponent>;
let router: Router;
let location: SpyLocation;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UserComponent],
imports: [
RouterTestingModule.withRoutes([
{ path: 'users/:id', component: UserComponent }
])
],
providers: [
{ provide: Location, useClass: SpyLocation }
]
}).compileComponents();
fixture = TestBed.createComponent(UserComponent);
component = fixture.componentInstance;
router = TestBed.inject(Router);
location = TestBed.inject(Location) as SpyLocation;
});
it('should navigate to user page', fakeAsync(() => {
router.navigate(['/users', 123]);
tick();
expect(location.path()).toBe('/users/123');
expect(component.userId).toBe('123');
}));
});// HTTP Testing types
export interface RequestMatch {
method?: string;
url?: string;
}
export interface TestResponseOptions {
headers?: HttpHeaders | { [name: string]: string | string[] };
status?: number;
statusText?: string;
}
// Location Testing types
export interface MockPlatformLocationConfig {
startUrl?: string;
appBaseHref?: string;
}
export const MOCK_PLATFORM_LOCATION_CONFIG: InjectionToken<MockPlatformLocationConfig>;
// Provider functions
export function provideLocationMocks(): Provider[];
export function provideHttpClientTesting(): Provider[];