Angular 2 component testing with observable no bullying

I'm 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 my component can use this service to actually connect to the API to the interface. So far, I can confirm that my methods inside my component and services are launched from my spy, but the .subscribe (....) method in my component will not be completed until the test completes / fails. My code for my specification 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 until they are marked with the "// ISSUES HERE" icon.

async() , , . componentSpy, ngOnInit ( ).

:

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)
    );
  }
}

, , ReviewService :

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')
      );
  };
}

Karma, , ngOnInit, , , ( "// " ), .

: , , "" . , . , , .subscribe(...), ReviewComponent ngOnInit(), , / .

+4
2

async, . , .

it('', async(() => {
  observable().subscribe(
    (review) => {
       expect(component.Reviews[0]).not.toBe(undefined)
    }
  )
}));

, . angular http i.e:

import { Http, HttpModule, BaseRequestOptions, XHRBackend, HttpModule, ConnectionBackend } from '@angular/http';
const httpSubject = new Subject()
const http$ = httpSubject.asObservable()
const httpFactory = (backend, options) => {
  const newHttp = new Http(backend, options);
  const helperHttp = new Http(backend, options);
  newHttp.get = function(strng, options) {
    return helperHttp.get(strng, options).do((response) => {
      setTimeout(() => {
        httpSubject.next({});
      }, 300);
    });
  };
  return newHttp;
}

TestBed.configureTestingModule({
  imports: [HttpModule, FormModule],
  providers: [
    UrlService,
    ConnectionBackend,
    BaseRequestOptions
    {
      provide: Http,
      useFactory: httpFactory,
      deps: [ XHRBackend, BaseRequestOptions] 
    }
  ],
  declarations: [ReviewComponent]
})

it('should show review after the getReviewByProcess resolves', 
  async(() => {
    http$.subscribe(() => {
      expect(componentSpy).toHaveBeenCalled();
      expect(component.Reviews[0]).not.toBe(undefined);
    })
    fixture.detectChanges();
})); 
+2

. - :

spyOn(service, 'getReviewByProcess')
   .and.returnValue(Observable.of(new Array<Review>()));
0

Source: https://habr.com/ru/post/1675074/


All Articles