Как подделать сервис, внедренный в компонент, который делает HTTP-вызовы? - PullRequest
1 голос
/ 09 октября 2019

Я новичок в Angular и пытаюсь научиться юнит-тестированию. У меня есть очень простой компонент, который зависит от службы ( UserService ). В хуке жизненного цикла ngOnInit () я вызываю метод службы и сохраняю данные, которые он возвращает, в свойстве. На мой взгляд, у меня есть список, связанный с этим свойством.

Это код моего компонента.

import { Component, OnInit } from "@angular/core";
import { UserService } from "../services/user.service";
import { User } from "./models/user";

@Component({
 selector: "app-home",
 templateUrl: "./home.component.html",
 styleUrls: ["./home.component.css"]
})
export class HomeComponent implements OnInit {
 users: User[] = [];
 constructor(private userService: UserService) {}

 ngOnInit() {
   this.userService.getUsers().subscribe(x => {
     this.users = x;
   });
 }
}

Это HTML

<ul>
  <li *ngFor="let user of users" class="user-item">{{ user.name }}</li>
</ul>

Служба пользователя выглядит следующим образом

import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { User } from "../home/models/user";

@Injectable({
  providedIn: "root"
})
export class UserService {
  private baseUrl = "https://jsonplaceholder.typicode.com";
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get<User[]>(`${this.baseUrl}/users`);
  }
}

Все работает как положено. Я пытаюсь написать некоторые модульные тесты для этого компонента. Я создал несколько поддельных данных

import { User } from "../home/models/user";

export const FakeData = {
  users: <User[]>[
    {
      id: 1,
      name: "User 1",
      username: "User1",
      email: "User1@fakemail.com",
      phone: "123 456 789",
      website: "afraz.com"
    },
    {
      id: 1,
      name: "User 2",
      username: "User2",
      email: "User2@fakemail.com",
      phone: "123 456 789",
      website: "afraz.com"
    }
  ]
};

Это тесты, которые я добавил

import {
  async,
  ComponentFixture,
  TestBed,
  fakeAsync,
  flush
} from "@angular/core/testing";
import { HttpClientModule } from "@angular/common/http";

import { HomeComponent } from "./home.component";
import { UserService } from "../services/user.service";
import { of } from "rxjs";
import { FakeData } from "../fakes/fake-data";
import { By } from "@angular/platform-browser";

describe("HomeComponent", () => {
  let component: HomeComponent;
  let fixture: ComponentFixture<HomeComponent>;
  let userService: UserService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientModule],
      declarations: [HomeComponent],
      providers: [UserService]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HomeComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    // Mock the user service
    userService = TestBed.get(UserService);
    spyOn(userService, "getUsers").and.returnValue(of(FakeData.users));
  });

  afterEach(() => {
    fixture.destroy();
    component = null;
  });

  it("should create component", () => {
    expect(component).toBeTruthy();
  });

  it("should get 'Users' from the service", fakeAsync(() => {
    fixture.whenStable().then(() => {
      expect(component.users.length).toEqual(2);
    });
  }));

  it("should bind 'Users' to the view", fakeAsync(() => {
    fixture.whenStable().then(() => {
      const userList = fixture.debugElement.queryAll(By.css(".user-item"));
      expect(userList.length).toEqual(2);
    });
  }));
});

Теперь вот где я запутался, я получаю следующую ошибку в выводе

'Spec' HomeComponent должен связывать 'Users' с представлением 'не имеет ожиданий.'

'Spec' HomeComponent должен получать 'Users' из сервиса 'не имеет никаких ожиданий. '

Проблема в том, что если я использую Async вместо fakeAsync , то вызывается фактическая служба пользователя, а я не хочусделать это. Я ожидаю, что, поскольку у меня есть следующая строка кода

spyOn(userService, "getUsers").and.returnValue(of(FakeData.users));

в блоке beforeEach () , следовательно, фактический код не должен выполняться, а вместо этогоэти жестко закодированные данные должны быть возвращены. Кто-нибудь может помочь и объяснить, что мне не хватает?

ОБНОВЛЕНИЕ: Вот мой последний рабочий код тестов. Спасибо за быструю помощь! Надеюсь, это поможет кому-то еще:)

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

import { HomeComponent } from "./home.component";
import { UserService } from "../services/user.service";
import { of } from "rxjs";
import { FakeData } from "../fakes/fake-data";
import { By } from "@angular/platform-browser";

describe("HomeComponent", () => {
  let component: HomeComponent;
  let fixture: ComponentFixture<HomeComponent>;
  let userServiceSpy: jasmine.SpyObj<UserService>;

  beforeEach(async(() => {
    userServiceSpy = jasmine.createSpyObj(["getUsers"]);

    TestBed.configureTestingModule({
      imports: [],
      declarations: [HomeComponent],
      providers: [{ provide: UserService, useValue: userServiceSpy }]
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HomeComponent);
    component = fixture.componentInstance;
    userServiceSpy.getUsers.and.returnValue(of(FakeData.users));
    fixture.detectChanges();
  });

  afterEach(() => {
    fixture.destroy();
    component = null;
  });

  it("should create component", () => {
    expect(component).toBeTruthy();
  });

  it("should have 'Users' populated", () => {
    expect(component.users.length).toEqual(2);
  });

  it("should bind 'Users' to the view", () => {
    const userList = fixture.debugElement.queryAll(By.css(".user-item"));
    expect(userList.length).toEqual(2);
  });
});

1 Ответ

0 голосов
/ 09 октября 2019

Вы должны быть в состоянии полностью избежать использования fakeAsync. Я полагаю, что в вашем случае вы звоните fixture.detectChanges() до того, как настроите своего шпиона - именно поэтому вызывается «реальный» сервис.

Создайте в тесте своего UserService шпиона и зарегистрируйте его какпровайдеру HomeComponent:

describe('HomeComponent', () => {
  let comp: HomeComponent;
  let fixture: ComponentFixture<HomeComponent>;

  let userServiceSpy: SpyObj<UserService>;

  beforeEach(async(() => {
    userServiceSpy= createSpyObj(['getUsers']);

    TestBed.configureTestingModule({
      imports: [],
      declarations: [ HomeComponent ],
      schemas: [ NO_ERRORS_SCHEMA ]
    })
    .overrideComponent(HomeComponent , {
      set: {
        providers: [
          { provide: UserService, useValue: userServiceSpy }
        ]
      }
    })
    .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(HomeComponent);
        comp = fixture.componentInstance;
    });
  }));

  beforeEach(() => {
    userServiceSpy.getUsers.and.returnValue(of(FakeData.users));
    fixture.detectChanges();
  });

  ...
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...