I have been trying to create integration tests using Jasmine for an Angular 2 project. I can mock services and data just fine and run the appropriate unit tests, but now I want to test to make sure that my component can use the service to actually connect to the back end API. So far, I can confirm that my methods inside both my component and service are being triggered from my spy's, but the .subscribe(....) method within my component will not complete until after the test is finished/failed. My code for my spec is as follows:
import { async, ComponentFixture, TestBed, inject, fakeAsync } from '@angular/core/testing';
import { DebugElement } from "@angular/core";
import { By } from "@angular/platform-browser";
import { ReviewComponent } from './review.component';
import { FormsModule } from '@angular/forms';
import { UrlService } from 'app/shared/services/url.service';
import { HttpModule, Http } from '@angular/http';
import { ReviewService } from "./review.service";
describe('CommunicationLogComponent', () => {
let component: ReviewComponent;
let fixture: ComponentFixture<ReviewComponent>;
let serviceSpy: jasmine.Spy;
let componentSpy: jasmine.Spy;
let service: ReviewService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FormsModule, HttpModule],
providers: [UrlService],
declarations: [ReviewComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ReviewComponent);
component = fixture.componentInstance;
component.processId = 6;
service = fixture.debugElement.injector.get(ReviewService);
componentSpy = spyOn(component, "ngOnInit").and.callThrough();
serviceSpy = spyOn(service, 'getReviewByProcess').and.callThrough();
});
it('should create component', () => {
expect(component).toBeTruthy();
});
it('should not have called ngOnInit() yet', () => {
expect(componentSpy).not.toHaveBeenCalled();
});
it('should make a call to ngOnInit() and by extension getReviewByProcess', () => {
fixture.detectChanges();
expect(componentSpy).toHaveBeenCalled();
expect(serviceSpy).toHaveBeenCalled();
});
//ISSUES HERE
it('should show review after the getReviewByProcess resolves', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(componentSpy).toHaveBeenCalled();
fixture.detectChanges();
expect(component.Reviews[0]).not.toBe(undefined);
});
}), 5000);
});
All tests up until the one marked with "//ISSUES HERE" are working fine and pass.
I am using async() and waiting for the fixture to become stable before attempting to finish out my test. It also confirms via the componentSpy that ngOnInit has been called (Which will connect to the backend and subscribe to a Review).
My component code looks like this:
import { Component, EventEmitter, Input, OnInit } from '@angular/core';
//Service
import { ReviewService } from "app/process/monitoring/shared/components/review/review.service";
//Classes
import { Review } from "app/process/monitoring/shared/components/review/review";
@Component({
providers: [
ReviewService
],
selector: 'monitoring-review',
templateUrl: './review.component.html',
styleUrls: ['./review.component.css']
})
export class ReviewComponent implements OnInit {
public Review: Review = new Review();
public Reviews: Review[] = [];
@Input() public processId: number;
constructor(
private ReviewService: ReviewService
) { }
ngOnInit(): void {
this.ReviewService.getReviewByProcess(this.processId, true).first().subscribe(
Reviews => {
if (Reviews) {
this.Reviews = Reviews //doesn't trigger until test is finished
}
},
error => console.log(error)
);
}
}
And finally my ReviewService looks like this:
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Headers, RequestOptions, RequestMethod, URLSearchParams } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
// App-wide Services
import { UrlService } from 'app/shared/services/url.service';
// Classes
import { Review } from "app/process/monitoring/shared/components/review/review";
@Injectable()
export class ReviewService {
private devUrl: string;
private jsonHeaders: Headers = new Headers({ 'Content-Type': 'application/json' });
private prodUrl: string;
private reviewPath: string = 'monitoring/program/';
constructor(
private urlService: UrlService,
private http: Http
) {
this.devUrl = this.urlService.getUrl('dev').url;
this.prodUrl = this.urlService.getUrl('prod').url;
}
getReviewByProcess(processId: number, isProd: boolean = false): Observable<Review[]> {
let targetUrl: string = (isProd ? this.prodUrl : this.devUrl) + this.reviewPath + "/" + processId;
return this.http.get(targetUrl, { withCredentials: true })
.map((res: Response) => res.json())
.catch((error: any) => Observable.throw(error.json().error || 'Server error')
);
};
}
Using the Karma debugger, I can confirm that my test calls the ngOnInit, which then calls to the service, but it never hits the inner part of my components subscribe (marked with the "//doesn't trigger") until after my test is completed.
To summarize: I want to create a test that can test my component integrating with my service using a "live" backend to collect my data. That is the service and my component will not be mocking anything. I can confirm that my methods are being reached but the .subscribe(...) found in my ReviewComponent's ngOnInit() method does not finish until after the test is completed/failed.