Как проследить вызов службы в Angular2

Следуя примеру кода, найденному в 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: '[email protected]'
    }]);

    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?

Ответы

Ответ 1

Я использую немного другой подход и использую саму инъекцию, чтобы вместо этого получить экземпляр службы через 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: '[email protected]'
      }]);

      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: [email protected]');
        });
    }))
  );

});

Изменить для Angular 4

В последних документах есть более простой способ получить доступ к службе с помощью компонента-инжектора:

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

Изменить для Angular 5+

  import { Component, Injector, Type } from '@angular/core';
  ...

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

Ответ 2

Другие решения не работали для меня, поэтому я ввел службу 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

Я нашел решение

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

User-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: '[email protected]'
      }]);

      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: [email protected]');
        });
    }))
  );

});

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

Ответ 4

Для всех новичков для тестирования в angular 2 (так же, как и я здесь)

Способы createSpy или SpyOn доступны только в том случае, если у вас есть правильные идентификаторы для установки жасмина. В противном случае вы получите ошибку typescript.

проверьте свой файл typings.json, а если нет, запустите этот

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