在本教程中,你将学习如何通过示例测试案例对angular服务进行单元测试。它还包括以下内容
- 如何设置单元测试的Angular服务
- 单元测试服务功能
- 用jasmine库模拟依赖关系
- 用karma库断言预期和实际
单元测试是测试一段代码的运行是否符合预期。
在我之前的文章中,我们讨论了关于Angular服务的完整教程和实例
我们有一个服务,其中包含可重用的业务方法,这些方法是独立的
- 非同步函数
- 同步函数
- 有依赖关系的服务方法
我们必须写一个单元测试代码来独立测试该服务。
我假设你已经创建了一个angular应用程序。
让我们用下面的命令创建一个Angular服务
ng g service employee
这里是一个输出
B:\angular-app-testing>ng g service employee
CREATE src/app/employee.service.spec.ts (347 bytes)
CREATE src/app/employee.service.ts (133 bytes)
这是一个服务代码,我们用它来编写测试案例employee.service.ts。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class EmployeeService {
constructor() { }
}
这里是服务规范文件,Angular service code employee.service.spec.ts。
import { TestBed } from '@angular/core/testing';
import { EmployeeService } from './employee.service';
describe('EmployeeService', () => {
let service: EmployeeService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(EmployeeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
考虑到这是一个雇员服务,它有以下的服务方法
- 创建一个雇员
- 更新一个雇员
- 获取所有雇员
- 删除一个雇员
假设雇员存储在数据库中,所以我们必须使用一个http连接到数据库。所以Angular在HttpClient 类中提供了http连接包装器。该服务对类有依赖性,服务做了以下事情
- 通过构造函数向服务注入HttpClient方法,如下例所示
这里是一个Angular Service example
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from "rxjs/operators";
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class EmployeeService {
private employee_api_url: string = 'api.localhost.com';
constructor(private httpClient: HttpClient) { }
createEmployee(employee: any): Observable {
return this.httpClient.post(this.employee_api_url + '/create', employee)
.pipe(map((resp: any) => resp.json()),
catchError(error => this.throwError(error))
)
}
getEmployees(): Observable {
return this.httpClient.get(this.employee_api_url + '/read')
.pipe(map((resp: any) => resp.json()),
catchError(error => this.throwError(error))
)
}
updateEmployee(employee: any): Observable {
return this.httpClient.get(this.employee_api_url + '/update')
.pipe(map((resp: any) => resp.json()),
catchError(error => this.throwError(error))
)
}
deleteEmployee(id: number): Observable {
return this.httpClient.delete(this.employee_api_url + '/delete/{id}')
.pipe(map((resp: any) => resp.json()),
catchError(error => this.throwError(error))
)
}
throwError(error: any) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
我们必须为一个类的每个方法独立编写一个测试用例。
当你为服务编写测试用例时,通常你需要做以下步骤
- 获取一个类的对象
- 调用一个函数方法,传递不同的值
- 最后用预期值断言结果。
默认情况下,angular cli会创建一个单元测试类文件,也叫规格文件,如下所示
默认生成的代码为angular unit testing service example
import { TestBed } from '@angular/core/testing';
import { EmployeeService } from './employee.service';
describe('EmployeeService', () => {
let service: EmployeeService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(EmployeeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
让我们来讨论一下规格文件的步骤
describe('EmployeeService', () => {});
describe 是Jasmine框架中的一个关键字,它是用来指定逻辑上的分离,以告诉测试案例的分组。它包含beforeeach和it 方法。
beforeEach 用于初始化和声明所有的依赖和实例,这些都是在it 方法中定义的测试用例。
it('should be created', () => {
expect(service).toBeTruthy();
});
TestBed是一个Angular测试模块,在测试容器中加载和运行Angular组件。
TestBed.configureTestingModule({});
由于Angular服务对HttpClient有依赖性,所以要创建一个服务对象,以发送http请求和响应,所以你需要提供HttpClient对象的实例。
如何为Angular服务创建一个HttpClient实例?
一般来说,在单元测试中,我们不会创建一个真实的类的实例,我们会创建一个HttpClient的模拟实例,模拟是一个假的对象,而不是真实的对象。我们不发送真实的http请求和响应,而是发送假的请求和响应。
为了创建一个httpClient的模拟实例,Angular提供了HttpClientTestingModule 首先,将HttpClientTestingModule导入到你的测试规范文件中,如下图所示 将EmployeeService 作为TestBed提供者的一个依赖项。
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [EmployeeService],
});
让我们来讨论一下测试用例
- 检查服务实例创建与否的测试用例
it('should be created', () => {
const service: EmployeeService = TestBed.get(EmployeeService);
expect(service).toBeTruthy();
});
- 角度服务方法的可观察性的测试用例
写一个雇员创建的步骤顺序
-
首先从EmployeeService对象中获取
TestBed -
使用Jasmine的
spyOn方法在createEmployee方法上创建一个模拟对象。spyon包含两个方法
- withArgs用于输入模拟方法的值
- returnValue包含返回值,是可观察的
-
接下来,调用真正的服务方法并订阅它,在订阅中检查返回值与实际值和预期值的断言。
-
最后使用
toHaveBeenCalled方法检查服务方法是否被调用。
it('Create an Employee on Service method', () => {
const service: EmployeeService = TestBed.get(EmployeeService);
let employeeServiceMock = spyOn(service, 'createEmployee').withArgs({})
.and.returnValue(of('mock result data'))
service.createEmployee({}).subscribe((data) => {
console.log("called")
expect(data).toEqual(of('mock result data'));
});
expect(service.createEmployee).toHaveBeenCalled();
});
NullInjectorError:在单元测试服务时没有HttpClient的提供者的错误
这看起来像是一个HttpClientModule在测试案例模块中没有被正确导入。
import { HttpClientTestingModule } from '@angular/common/http/testing';
在组件规格文件的@ngModule中。
你可以包括HttpClientModule ,如下所示
//first testbed definition, move this into the beforeEach
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [EmployeeService]
});
总结
你学会了用httpclient和依赖关系以及模拟可观察方法对angular服务进行单元测试。