如何在Angular2中监视服务调用

时间:2016-03-01 21:25:04

标签: unit-testing angular

按照Ari Lerner ng-book2上的代码示例,并使用Angular 2 beta 7,我试图模拟并监视对服务的调用失败。

这是使用该服务的主要组件:

用户list.component.ts

import {Component, OnInit} from 'angular2/core';
import {UserService} from './user.service';
import {IUser} from './user.model';

@Component({
  selector: 'user-list',
  providers: [UserService],
  template: `
    <div *ngFor="#user of users" class="user">
      <span class="username">Username: {{ user.username }}</span><br>
      <span class="email">Email: {{ user.email }}</span>
    </div>
  `
})
export class UserListComponent implements OnInit {
  public users: IUser[];
  private userService: UserService;

  constructor(userService: UserService) {
    this.userService = userService;
  }

  ngOnInit(): void {
    this.userService.getAllUsers().subscribe(
      (users: IUser[]) => {
        this.users = users;
      },
      (error: any) => {
        console.log(error);
      }
    );
  }
}

这就是服务本身。

user.service.ts

import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/Rx';
import {IUser} from './user.model';

@Injectable()
export class UserService {
  private http: Http;
  private baseUrl: string = 'http://jsonplaceholder.typicode.com/users';

  constructor(http: Http) {
    this.http = http;
  }

  public getAllUsers(): Observable<IUser[]> {
    return this.http.get(this.baseUrl)
      .map(res => res.json());
  }
}

为了测试UserListComponent,我尝试使用以下代码模拟UserService并监听其方法调用getAllUser

用户list.component.spec.ts

import {
  describe, 
  expect, 
  it,
  injectAsync,
  TestComponentBuilder,
  ComponentFixture,
  setBaseTestProviders,
} from 'angular2/testing';

import {SpyObject} from 'angular2/testing_internal';

import {
  TEST_BROWSER_PLATFORM_PROVIDERS, 
  TEST_BROWSER_APPLICATION_PROVIDERS
} from 'angular2/platform/testing/browser';

import {provide} from 'angular2/core';

import {UserListComponent} from './user-list.component';
import {UserService} from './user.service';

class SpyUserService extends SpyObject {
  public getAllUsers: Function;
  public fakeResponse: any = null;

  constructor() {
    super(UserService);
    this.getAllUsers = this.spy('getAllUsers').andReturn(this);
  }

  public subscribe(callback) {
    callback(this.fakeResponse);
  }

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

describe('When rendering the UserListComponent and mocking the UserService', () => {

  setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);  

  it('should show one mocked user', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {

    let spyUserService = new SpyUserService();
    spyUserService.setResponse([{
      username: 'ryan',
      email: 'ryan@gmail.com'
    }]);

    return tcb
      .overrideProviders(UserListComponent, [provide(UserService, {useValue: spyUserService})])
      .createAsync(UserListComponent)
      .then((fixture: ComponentFixture) => {
        fixture.detectChanges();
        expect(spyUserService.getAllUsers).toHaveBeenCalled();
      });
  }));

});

当使用业力来运行测试时,我收到以下控制台错误:

Chrome 48.0.2564 (Mac OS X 10.11.3) ERROR
  Uncaught TypeError: Cannot read property 'isSlow' of null
  at /Users/david/apps/sandbox/angular2-testing-cookbook/src/tests.entry.ts:19430

有人知道为什么会抛出这个错误,或者模拟和间谍服务单元测试Angular 2组件的正确方法是什么?

4 个答案:

答案 0 :(得分:24)

我采用了稍微不同的方法,并使用注入自己通过DI来获取服务实例。所以你的测试看起来像:

import {
  describe, 
  expect, 
  it,
  tick,
  inject,
  fakeAsync,
  TestComponentBuilder,
  ComponentFixture,
  addProviders
} from 'angular2/testing';

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

import {UserListComponent} from './user-list.component';

import {UserService} from './user.service';
import {MockUserService} from './user.service.mock';

describe('When loading the UserListComponent', () => {

  beforeEach(() => addProviders([
      {provide: UserService, useClass: MockUserService}
  ]));

  it('should call the getAllUsers method from the UserService', 
    inject([TestComponentBuilder, UserService], fakeAsync((tcb: TestComponentBuilder, mockUserService: UserService) => {
      spyOn(mockUserService, 'getAllUsers');

      tcb
        .createAsync(UserListComponent)
        .then((fixture: ComponentFixture) => {
          tick();
          fixture.detectChanges();
          expect(mockUserService.getAllUsers).toHaveBeenCalled();
        });
    }))
  );

  it('should show one mocked user', 
    inject([TestComponentBuilder, UserService], fakeAsync((tcb: TestComponentBuilder, mockUserService: UserService) => {
      mockUserService.setResponse([{
        username: 'ryan',
        email: 'ryan@gmail.com'
      }]);

      tcb
        .createAsync(UserListComponent)
        .then((fixture: ComponentFixture) => {
          tick();
          fixture.detectChanges();
          let compiled = fixture.debugElement.nativeElement;
          expect(compiled.querySelector('div:nth-child(1) .username')).toHaveText('Username: ryan');
          expect(compiled.querySelector('div:nth-child(1) .email')).toHaveText('Email: ryan@gmail.com');
        });
    }))
  );

});

编辑Angular 4

最新的docs有一种更简单的方法来使用组件的注入器来获取服务:

  fixture = TestBed.createComponent(TestComponent);
  component = fixture.componentInstance;
  const mockService = fixture.debugElement.injector.get(MyService);

答案 1 :(得分:5)

我找到了解决方案

<强> test.entry.ts

import {setBaseTestProviders} from 'angular2/testing';

import {
  TEST_BROWSER_PLATFORM_PROVIDERS, 
  TEST_BROWSER_APPLICATION_PROVIDERS
} from 'angular2/platform/testing/browser';

setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);  

import './user-list.component.spec';

用户-list.component.spec.ts

import {
  describe, 
  expect, 
  it,
  tick,
  inject,
  fakeAsync,
  TestComponentBuilder,
  ComponentFixture,
  beforeEachProviders
} from 'angular2/testing';

import {UserListComponent} from './user-list.component';
import {MockUserService} from './user.service.mock';

describe('When loading the UserListComponent', () => {

  let mockUserService: MockUserService;

  beforeEachProviders(() => {
    mockUserService = new MockUserService();
    return [mockUserService.getProvider()];
  });

  it('should call the getAllUsers method from the UserService', 
    inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
      spyOn(mockUserService, 'getAllUsers');

      tcb
        .createAsync(UserListComponent)
        .then((fixture: ComponentFixture) => {
          tick();
          fixture.detectChanges();
          expect(mockUserService.getAllUsers).toHaveBeenCalled();
        });
    }))
  );

  it('should show one mocked user', 
    inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
      mockUserService.setResponse([{
        username: 'ryan',
        email: 'ryan@gmail.com'
      }]);

      tcb
        .createAsync(UserListComponent)
        .then((fixture: ComponentFixture) => {
          tick();
          fixture.detectChanges();
          let compiled = fixture.debugElement.nativeElement;
          expect(compiled.querySelector('div:nth-child(1) .username')).toHaveText('Username: ryan');
          expect(compiled.querySelector('div:nth-child(1) .email')).toHaveText('Email: ryan@gmail.com');
        });
    }))
  );

});

<强> user.service.mock.ts

import {provide, Provider} from 'angular2/core';
import {UserService} from './user.service';
import * as Rx from 'rxjs/Rx';

export class MockUserService {

  public fakeResponse: any = null;

  public getAllUsers(): Rx.Observable<any> {
    let subject = new Rx.ReplaySubject()
    subject.next(this.fakeResponse);
    return subject;
  }

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

  public getProvider(): Provider {
    return provide(UserService, {useValue: this});
  }
}

答案 2 :(得分:5)

其他解决方案对我不起作用,所以我从injector注入debugElement服务。

import { TestBed, 
         async }  from '@angular/core/testing';

@injectable()
class MyService {
  public method () {}
}

let MyMockedService = {
  method: () => {}
}

@Component({
  template: ''
})
class MyComponent {
  constructor(private myService: MyService) {;}
  public method () {
    this.myService.method();
  }
}

describe('Test', () => {
  beforeEach(async(() => {
    TestBed
      .configureTestingModule({
        imports: [
          CommonModule
        ],
        declarations: [
          MyComponent
        ],
          providers: [
            { provide: MyService, useValue: MyMockedService}
        ]
      })
      .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(MyComponent);
        myComponent = fixture.componentInstance;
      });
  }));
  it('should spy on service', () => {
    let myMockedService = fixture.debugElement.injector.get(MyMockedService);
    spyOn(myMockedService, 'method');
    myComponent.method();
    expect(myMockedService.method);
  });
})

答案 3 :(得分:4)

对于所有以角度2进行测试的新手(与我在这里相同)

如果您安装了正确的jasmine类型,则createSpy或SpyOn方法仅“可用”。否则会引发打字稿错误。

检入您的typings.json文件,如果不存在,请运行此

typings install --save --global registry:dt / jasmine