unit-testing - Angular 2——模拟——没有 HTTP 提供者

标签 unit-testing testing angular mocking ionic2

Angular 2.0.0 - Ionic 2 RC0 - Npm 3.10.8 - Node v4.5.0 - Karma 1.3.0 - Jasmine 2.5.2


我正在尝试使用 Karma & Jasmine 测试我的应用程序。现在我到了遵循一些指南的地步(我是这些测试框架的新手)。但很遗憾,我在尝试执行测试时遇到错误。

我正在尝试测试 EventsPage,它没有 Http 导入,但它调用了我的 APICaller.service,它确实使用了 Http .这就是为什么我创建了一个 MockAPICaller 但它似乎仍然需要 Http (可能是因为它在 APICaller 的构造函数中,但我不会知道如何解决这个问题)。

所以我怀疑问题出在 MockAPICaller 中,但我不确定。


我将发布 MockAPICaller.serviceAPICaller.serviceEventsPage 和我的 events.spec.ts。 (按照这个顺序,如果你需要/想要的话,你可以跳过。

模拟API调用者

import { SpyObject } from './helper';
import { APICaller } from '../apicaller.service';
import Spy = jasmine.Spy;

export class MockAPICaller extends SpyObject {
    getEventsSpy: Spy;
    searchEventSpy:Spy;
    getParticipantSpy:Spy;
    getEventParticipantsSpy:Spy;
    searchEventParticipantSpy:Spy;
    addNewCommentSpy:Spy;
    updateCommentSpy:Spy;
    deleteCommentSpy:Spy;
    getUsernameSpy:Spy;
    presentSuccessMessageSpy:Spy;

    fakeResponse:any;

    constructor(){
        super( APICaller );
        this.fakeResponse = null;
        this.getEventsSpy = this.spy('getEvents').andReturn(this);
        this.searchEventSpy = this.spy('searchEvent').andReturn(this);
        this.getParticipantSpy = this.spy('getParticipant').andReturn(this);
        this.getEventParticipantsSpy = this.spy('getEventParticipant').andReturn(this);
        this.searchEventParticipantSpy = this.spy('searchEventParticipant').andReturn(this);
        this.addNewCommentSpy = this.spy('addNewComment').andReturn(this);
        this.updateCommentSpy = this.spy('updateComment').andReturn(this);
        this.deleteCommentSpy = this.spy('deleteComment').andReturn(this);
        this.getUsernameSpy = this.spy('getUsername').andReturn(this);
        this.presentSuccessMessageSpy = this.spy('presentSuccessMessage').andReturn(this);
    }

    subscribe(callback: any){
        callback(this.fakeResponse);
    }

    setResponse(json:any):void{
        this.fakeResponse = json;
    }
}

API调用者

import { Injectable, Inject } from '@angular/core';
import { Http } from '@angular/http';
import { ToastController } from 'ionic-angular';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';

import { Event } from '../models/event.model';
import { Participant } from '../models/participant.model';
import { Comment } from '../models/comment.model';

@Injectable()
export class APICaller {
    http : Http;

    //baseUrl to the REST API
    baseUrl : string = "http://some.correct.url:8080/myAPI";

    constructor(public httpService: Http, public toastCtrl:ToastController) {
        this.http = httpService;
    }


    //-------------------EVENTS-----------------------------------//

    //retrieves all the events
    getEvents() : Observable<Array<Event>> {
        return this.http
        .get(`${ this.baseUrl }/events`)
        .map(response => {
            return response.json();
        });
    }

    //searches events with the provided term
    searchEvent(searchTerm : string) : Observable<Array<Event>> {
        return this.http
        .get(`${ this.baseUrl }/events/search/${ searchTerm }`)
        .map(response => {
            return response.json();
        });
    }

    //--------------------PARTICIPANTS-----------------------------------//

    //retrieves the participant from the REST API
    getParticipant(participantId : number) : Observable<Participant>{
        return this.http
        .get(`${ this.baseUrl }/participants/${ participantId }`)
        .map(response => {
            return response.json();
        });
    }

    getEventParticipants(eventId:number) : Observable<Array<Participant>> {
        return this.http
        .get(`${ this.baseUrl }/events/${ eventId }/participants`)
        .map(response => {
            return response.json();
        });
    }

    //searches for deelnemers with the provided term
    searchEventParticipant(eventId : number, searchTerm : string) : Observable<Array<Participant>> {
        return this.http
        .get(`${ this.baseUrl }/events/${ eventId }/participants/search/${ searchTerm }`)
        .map(response => {
            return response.json();
        });
    }


    //-------------------COMMENTS--------------------------------------//

    //adding a new comment to a participant
    addNewComment(participantId : number, content : string) : Observable<Comment> {
        return this.http
        .post(`${ this.baseUrl }/participants/${ participantId }/addComment`
        ,{
            user: this.getUsername("apikey"),
            content: content
        }).map((response) => {
            this.presentSuccessMessage("Comment added");
            return (response.json());
        });
    }

    //updating an existing comment
    updateComment(participantId : number, commentId : number, content : string) : Observable<Comment> {
        return this.http
        .put(`${ this.baseUrl }/participants/${ participantId }/updateComment/${ commentId }`,{
            id: commentId,
            content: content
        }).map(response => {
            this.presentSuccessMessage("Comment updated");
            return response.json();
        });
    }

    //deleting a currently existing comment
    deleteComment(participantId : number, commentId : number) : Observable<Comment> {
        return this.http
        .delete(`${ this.baseUrl }/participants/${ participantId }/deleteComment/${ commentId }`)
        .map(response => {
            this.presentSuccessMessage("Comment deleted");
            return response.json();
        });
    }

    //presents a successmessage for 3 seconds
    presentSuccessMessage(messageContent : string) {
        //defining the message
        let message = this.toastCtrl
        .create({
            message: messageContent,
            duration: 3000
        });
        //showing the message on screen
        message.present();
    }

    //-------------------USER-------------------------------
    getUsername(someRandomKey : string) : string {
        return "developer";
      /*
        return this.http
        .get(`${ this.baseUrl }/getUsername/${ someRandomKey }`)
        .map(response => {
            return ;
        });
        */
    }
}

事件页面

import { Component } from '@angular/core';

import { NavController, Loading, LoadingController } from 'ionic-angular';

import { APICaller } from '../../services/apicaller.service';
import { EventDetailComponent } from '../event-detail/event-detail.component';
import { Event } from '../../models/event.model';

/*
  Class for Evenementen Overzicht.
*/
@Component({
  selector: 'events-component',
  templateUrl: 'events.component.html',
  providers: [ APICaller ]
})

 /** -------------------------------------------------------------------------------------- */

export class EventsPage {

//list of all events
public events : Array<Event>;
//the event that has been clicked on the page
public selectedEvent : Event;
//boolean to show 'no events' error message
public noEvents:boolean;

 /** -------------------------------------------------------------------------------------- */

  constructor(public navCtrl : NavController, public apiCaller:APICaller, public loadingCtrl : LoadingController) {
    //retrieve all events --> async method, can't use this.events yet.
    this.getEvents();
  }

  /** -------------------------------------------------------------------------------------- */

  /**Get Events - Sets the 'events' variable to all events found by the API. */
  getEvents(){
    //setup a loadingscreen
    let loading = this.loadingCtrl.create({
      content: "Loading..."
    }); 
    //present the loadingscreen
    loading.present();

    //reset the noEvents boolean.
    this.noEvents = true;

    //call the api and get all events
    this.apiCaller.getEvents()
    .subscribe(response => {
      //response is list of events
      this.events = response;
      //if the event is not empty, set noEvents to false.
      if(this.events.length > 0){
        this.noEvents = false;
      }
      //close the loading message.
      loading.dismiss();
    });
  }

 /** -------------------------------------------------------------------------------------- */

  /**Select Event - Sets the selectedEvent variable to the selected item. */
  selectEvent(event: any, eventObj){
    this.selectedEvent = eventObj;
  }

  /**Search Events - Triggers the API and sets events equal to events found by API*/
  searchEvents(ev){
  //reset noEvents
  this.noEvents = true;
  //if the searchfield is not empty, call api
  if(ev.target.value != ''){
    this.apiCaller.searchEvent(ev.target.value)
    .subscribe(response => {

      this.events = response;
      //
      if(this.events.length > 0){
        this.noEvents = false;

      }
    });
  }else{
    //if the searchfield is empty, get all the events
    this.getEvents();
  }
}

/** -------------------------------------------------------------------------------------- */

/*Cancel Search - clears input field and resets noEvents*/
cancelSearch(ev){
  ev.target.value = "";
  this.noEvents = false;
}
 /** -------------------------------------------------------------------------------------- */

 /**Do Refresh - Refreshes the list of  */
doRefresh(refresher) {
    this.getEvents();

    //giving feedback for user (1 sec instead of faster)
    setTimeout(() => {
      //stop the refresher
      refresher.complete();
    }, 1000);
  }

 /** -------------------------------------------------------------------------------------- */

 /**Go to EventDetail - Pushes the EventDetail page on the navigation stack. */
  goToEventDetail(eventOb: any, eventParam){
    this.navCtrl.push(EventDetailComponent
    , {
      event: eventParam
    });
  }

}
 /** -------------------------------------------------------------------------------------- */

事件.spec.ts

import { TestBed, inject, tick, fakeAsync } from '@angular/core/testing';
import { BaseRequestOptions, Http, ConnectionBackend, Response, ResponseOptions} from '@angular/http';
import { MockBackend } from '@angular/http/testing';
import { FormsModule } from '@angular/forms';
import { NavController, LoadingController } from 'ionic-angular';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { mockNavController } from 'ionic-angular/util/mock-providers';
import { EventsPage } from './events.component';
import { MockAPICaller } from '../../services/mocks/apicaller.service';
import { APICaller } from '../../services/apicaller.service';

describe('Component: EventsComponent', () => {
  let mockAPICaller : MockAPICaller = new MockAPICaller();

  beforeEach(() => {
  TestBed.configureTestingModule({
    declarations: [EventsPage],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],//for usage of Ionic2
    providers: [
      {provide: NavController, useValue: mockNavController },
      {provide: LoadingController, useValue: LoadingController},
      {provide: APICaller, useValue: mockAPICaller}
    ],
    imports: [FormsModule]
  });
  });


  it('should return all events', ()=>{
      let fixture = TestBed.createComponent(EventsPage);
      let eventsPage = fixture.debugElement.componentInstance;
      fixture.detectChanges();

      mockAPICaller.setResponse(JSON.stringify(`{
        id: 4,
        title: 'Weekend',
        eventdate: '24/09/2016',
        kind: 'closed',
        startingtime: '18:00',
        endtime: '21:00',
        description: 'Go home'
      }`));
      let results = eventsPage.getEvents();
      expect(results.length).toBe(1);
      expect(results[0].id).toBe(4);

  });
});

最佳答案

问题是这样的

@Component({
  providers: [ APICaller ] <========
})
export class EventsPage {

有了它,组件将尝试创建它自己的 APICaller 实例。这会覆盖您在 TestBed(即模拟)中所做的任何配置。

您可以做的是在创建组件之前覆盖它

beforeEach(() => {
  TestBed.configureTestingModule({})

  TestBed.overrideComponent(EventsPage, {
    set: {
      providers: [
        { provide: APICaller, useValue: mockApiCaller }
      ]
    }
  })
})

另请参阅:

关于unit-testing - Angular 2——模拟——没有 HTTP 提供者,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39891904/

相关文章:

ios - Xcode 11 单元测试 : Devices with iOS 12. * 未列出

ruby-on-rails - 单元测试隔离 : 20 lines of test for 1 line of code?

angularjs - 如何解耦js和angularjs

java - 内存数据库评估

ruby-on-rails - Rails 和 Github 操作 - 凭证

objective-c - 将 Swift 单元测试添加到混合语言 Xcode 项目

angularjs - 分析 karma-runner/jasmine 中的内存泄漏

javascript - 为什么这个变量总是返回 null ? Angular 服务

Angular 2 OpaqueToken 与 Angular 4 InjectionToken

javascript - 将脚本应用于 Angular 2 上的输入