如何伪造注入到进行HTTP调用的组件中的服务?

时间:2019-10-09 05:04:44

标签: angular typescript jasmine angular8

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

现在这是我很困惑的地方,我在输出中遇到以下错误

“规范'HomeComponent应该将'用户'绑定到视图'没有期望。”

“规范'HomeComponent应该从服务中获取'用户''没有期望。”

问题在于,如果我使用 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 :(得分:0)

您应该能够完全避免使用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();
  });

  ...