My Angular front-end is relatively small, but it relies heavily on data from an external server.
I'm encountering issues with my tests throwing a gyp error.
While I can mock the service to test a simple component, this approach only allows me to replace the output and doesn't constitute a real test. To conduct a proper test, I believe I need to establish a predefined return when the service calls the external API.
Here's a basic example:
The definition of the object/interface:
// alerts.ts
export interface Alert {
id: number;
display_message: string;
is_enabled: boolean;
}
The service definition:
// alerts.service.ts
import { of as observableOf, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators;
import { Alert } from './alerts';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
export interface IAlertService {
getAlerts(): Observable<Alert[] | null>;
}
@Injectable()
export class AlertService implements IAlertService {
readonly baseUrl = '/api/alerts/';
alerts$: Observable<Alert[] | null>;
constructor(private _http: HttpClient) { }
getAlerts(): Observable<Alert[] | null> {
return this._http.get<Alert[] | null>(this.baseUrl).pipe(
catchError(error => {
console.log('in catch: ', error);
return observableOf(null);
}));
}
}
The component code:
// alerts/alerts.component.html
<div *ngIf="alerts" >
<div class="service-notice" *ngFor="let alert of alerts">
<p [innerHTML]="alert.display_message"></p>
</div>
</div>
The component code then followed up by:
// alerts/alerts.component.ts
import { Component, OnInit } from '@angular/core';
import { Alert } from '../alerts';
import { AlertService } from '../alerts.service';
@Component({
selector: 'naas-alerts',
templateUrl: './alerts.component.html',
styleUrls: ['./alerts.component.scss'],
})
export class AlertComponent implements OnInit {
alerts: Alert[] | null;
constructor(private alertService: AlertService) { }
ngOnInit() {
this.alertService.getAlerts().subscribe(data => {
this.alerts = data;
});
}
}
To tackle this problem, I created an httpInterceptor class that enables me to define the desired string return value within the test:
// testing_interceptors.ts
import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse, HTTP_INTERCEPTORS
} from '@angular/common/http';
import { Injectable, Injector } from '@angular/core';
import { of as observableOf, Observable } from 'rxjs';
@Injectable()
export class TestHttpInterceptor implements HttpInterceptor {
current_containers: string = '[]';
current_user: string = '';
alerts: string = '[]';
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('TestHttpInterceptor called');
const url_regex = /(\/api(?:\/[\w+\/]+)+)$/;
const url_path = url_regex.exec(request.url)[1];
if(request.method === "GET") {
if(url_path == '/api/alerts/') {
return observableOf(new HttpResponse({
status: 200,
body: this.alerts
}));
}
if(url_path == '/api/users/current/') {
return observableOf(new HttpResponse({
status: 200,
body: this.current_user
}));
}
if (url_path === '/api/users/current/containers/') {
return observableOf(new HttpResponse({
status: 200,
body: this.current_containers
}));
}
}
}
}
...Followed by the corresponding test:
// alerts/alerts.component.spec.test
import { HttpClientModule, HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { AlertComponent } from './alerts.component';
import { AlertService } from '../alerts.service';
import { AlertServiceMock } from '../alerts.service.mock';
import { TestHttpInterceptor } from '../testing_interceptors';
describe('AlertsComponent', () => {
let fixture: ComponentFixture<AlertComponent>;
let component: AlertComponent;
let testHttpInterceptor: TestHttpInterceptor;
beforeEach(() => {
testHttpInterceptor = new TestHttpInterceptor();
TestBed.configureTestingModule({
declarations: [ AlertComponent ],
providers: [
{provide: AlertService, useClass: AlertService },
{provide: HttpClient, useClass: HttpClientModule},
{provide: HTTP_INTERCEPTORS, useClass: TestHttpInterceptor, multi: true }
],
}).compileComponents();
fixture = TestBed.createComponent(AlertComponent);
component = fixture.componentInstance;
});
it('should be created', done => {
fixture.detectChanges();
expect(component).toBeTruthy();
done();
});
});
I've been trying various solutions found online without much success. Any advice would be greatly appreciated!
- Is this testing approach valid? (I've looked into
marbles
but struggled to understand it) - How can I fix the current issue?