我是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);
});
});
答案 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();
});
...