模拟对象如何在 Angular 茉莉花单元测试中工作
How mock object work in Angular jasmine unit test
我正在学习单元测试和 Angular,所以我在这两个方面都是初学者。
我在 angular.
中参考了一些关于单元测试 http 的教程和文章
我无法理解 httpMock 使用 HttpTestingController 做了什么。我们调用实际服务的功能那么为什么我们称它为模拟?
底层流程是什么?
请参考一些文章以获得更好的理解。
提前致谢。
编辑:
这是我坚持使用 httpMock 的 issue。
模拟服务的想法是您不需要使用实际功能(真正的 http 调用),但您确实需要 [=24] 的服务方法=] 您的代码无一例外。
例如:
你有一个组件,它在某些时候通过 Http 从一些 API 收集数据。
单元测试之一应该测试您的组件是否进行了该调用,但是如果那里有真正的调用,您就不会该死。单元测试的重点是测试调用是否发生。
编辑:
如果某些内部逻辑进行调用以收集数据以显示某些内容,也会发生同样的情况。您应该模拟 http 调用和 return 您自己的数据。您的单元测试不应该依赖外部的东西。想象一下,您的测试将 运行 在没有互联网的环境中进行。测试应该随时通过。
无论您测试什么服务,这种情况都适用。单元测试应该有单一的职责。他们不应该依赖于任何与他们的主要目的不同的东西。
让我们以我的一个文件为例,好吗?
import { SimpleConfiguration } from '../../../../../model/SimpleConfiguration';
import { SessionService } from '../../../session/session.service';
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ConfigurationService } from './configuration.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
describe('ConfigurationService', () => {
let httpMock: HttpTestingController;
let service: ConfigurationService;
const createFakeFile = (fileName: string = 'fileName'): File => {
const blob = new Blob([''], { type: 'text/html' });
blob['lastModifiedDate'] = '';
blob['name'] = fileName;
return <File>blob;
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
HttpClientTestingModule
],
providers: [ConfigurationService, SessionService]
});
httpMock = TestBed.get(HttpTestingController);
service = TestBed.get(ConfigurationService);
});
it('should be created', done => {
expect(service).toBeTruthy();
done();
});
it('getConfigurations should GET on postes-sources/:postID/configuration', (done) => {
service.getConfigurations(0).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
expect(successRequest.request.method).toEqual('GET');
successRequest.flush(null);
httpMock.verify();
});
it('uploadFile should POST on postes-sources/:postID/configuration', (done) => {
service.uploadFile(0, createFakeFile(), new SimpleConfiguration()).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
expect(successRequest.request.method).toEqual('POST');
successRequest.flush(null);
httpMock.verify();
});
it('updateComment should POST on postes-sources/:postID/configurations/:confID', (done) => {
service.updateConfiguration(0, 0, new SimpleConfiguration()).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations/0');
expect(successRequest.request.method).toEqual('POST');
successRequest.flush(null);
httpMock.verify();
});
it('getComponentInformations should GET on postes-sources/:postID/tranches/:trancheID/parametres', (done) => {
service.getComponentInformations(0, 0).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/tranches/0/parametres');
expect(successRequest.request.method).toEqual('GET');
successRequest.flush(null);
httpMock.verify();
});
});
让我一步步为您详细解释。
我们从 describe
-ing 我们的测试开始。它允许我们对测试进行分组。在这种情况下,我们按功能对测试进行分组,我们的功能是名为 ConfigurationService 的服务。然后我们提供一个回调函数:Jasmine 将 运行 用于 运行 你的测试的函数。
接下来,我们声明我们的变量。在这里,我们声明了 2 个变量和一个函数:httpMock
、service
和 createFakeFile()
。这些变量在整个测试组中都会有帮助,所以我们在顶层声明它们。
然后是beforeEach
:在每次测试之前,这个函数都会运行,做一些事情。在这一个中,它将创建一个 TestBed :这是一些样板代码,将创建某种 Angular 模块,以允许您的测试 运行 就好像您的功能在真实的 Angular 应用程序中。
在这个测试平台,你需要声明你的依赖关系 : 因为我测试的是HTTP服务,所以我必须导入HTTP测试模块,因为我的测试使用路由,我还必须导入路由器测试模块。我还需要导入正在测试的服务,我导入了一个 SessionService
因为我也在我的服务中使用它。
之后,我 通过 TestBed.get
获取那些依赖项的服务实例 :这将允许我监视它们的属性,并查看它们的值以及它们是否'被称为。这也将允许我调用我想要测试的函数。
第一个测试来了。第一个非常简单并且默认实现:我测试服务是否正确创建。如果不是,则意味着您缺少依赖项,或者您错误地模拟了依赖项。 expect(service).toBeTruthy()
是真正的测试:with expect, jasmine expects (duh) 服务是真实的(即它不应该是等于未定义或空)。
下一个测试是一个真正的测试:在这个测试中,我希望我的函数调用某个端点,使用某个动词。
我首先要说的是,一旦调用完成,我就必须结束测试。这是通过调用 done
完成的,这是上面给出的回调。
然后,我 模拟 一个 HTTP 调用:这意味着我让 Angular 相信我正在进行 HTTP 调用,但实际上我假装如此。但在它看来,这就像在做一个真实的:这就是模拟:你模拟一个行为或一个值。如您所见,我在特定端点上模拟了一个 HTTP 调用,并且我期望一个特定的 HTTP 动词。
最后,如果我的函数getConfigurations
GETS 到那个URL,我的测试就会成功,否则就会失败。这意味着如果我在实际服务中更改端点,测试将失败:此测试可防止 副作用。
其他的测试也是一样的,我想我就不用解释了。
如果你想知道,我不是一个人做的,就像你一样,我寻求帮助并遵循教程。但是一旦你习惯了它,测试你的功能就会变得非常快速和容易。
希望对您有所帮助,如有任何需要我解释的问题,请随时提出!
我实际上 运行 在过去的一周里才对这个问题感兴趣,所以这对我来说还很新鲜。 Jasmine 也让我有些困惑,所以我可以分享我为解决问题所做的工作。
首先,Angular 教程有点误导新测试人员。它声称我们应该使用 Jasmine,但随后开始使用 TestBed,这有点误导。我最终选择了 TestBed,我可以向您展示我使用的是什么。
所以你有你的描述:
descripe('randomService', () -> {
}
您需要初始化您的`运行domService':
descripe('randomService', () -> {
let randomService: RandomService;
}
使用 beforeEach(),您可以在描述中的每个 it
语句之前重新初始化、赋值等。
descripe('randomService', () -> {
let randomService: RandomService;
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
}
所以我告诉 Angular 在每个 it
块之前重新导入 HttpClientTestingModule
。我的 randomService
需要 HttpClient
所以我需要创建一个 Jasmine Spy 对象,return 是我告诉它的,而不是让我的 randomService
命中实际后端并改变真实我后端的数据。
descripe('randomService', () -> {
let randomService: RandomService;
httpClientSpy;
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
randomService = new RandomService(<any> httpclientSpy);
}
所以现在每当我在 运行domService 中执行 'get' 方法时,它实际上都会使用 httpClientSpy,并且它会编译,因为我告诉 运行domService 我的论点是'any' 类型,据其所知,这实际上是一个真正的 HttpClient,即使它不是。要正确使用它,您必须为您的假 get 设置一个假 return:
descripe('randomService', () -> {
let randomService: RandomService;
httpClientSpy;
mockResponse = { 1: ['first', 'second', 'third'] };
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
randomService = new RandomService(<any> httpclientSpy);
});
it('should return first, second, third', () => {
spyOn(httpClientSpy, 'get').and.returnValue(Observable.of(mockResponse));
// randomService. <your get method here...>
randomService.getValue().subscribe((response) =>
expect(resposne[0].length).toEqual(3);
};
});
那个响应应该是在我们的 beforeEach()
中创建的 mockResponse 它不必在 beforeEach() 中,但在这个例子中我把它留在了那里。
我正在学习单元测试和 Angular,所以我在这两个方面都是初学者。 我在 angular.
中参考了一些关于单元测试 http 的教程和文章我无法理解 httpMock 使用 HttpTestingController 做了什么。我们调用实际服务的功能那么为什么我们称它为模拟? 底层流程是什么? 请参考一些文章以获得更好的理解。
提前致谢。
编辑: 这是我坚持使用 httpMock 的 issue。
模拟服务的想法是您不需要使用实际功能(真正的 http 调用),但您确实需要 [=24] 的服务方法=] 您的代码无一例外。
例如: 你有一个组件,它在某些时候通过 Http 从一些 API 收集数据。 单元测试之一应该测试您的组件是否进行了该调用,但是如果那里有真正的调用,您就不会该死。单元测试的重点是测试调用是否发生。
编辑: 如果某些内部逻辑进行调用以收集数据以显示某些内容,也会发生同样的情况。您应该模拟 http 调用和 return 您自己的数据。您的单元测试不应该依赖外部的东西。想象一下,您的测试将 运行 在没有互联网的环境中进行。测试应该随时通过。
无论您测试什么服务,这种情况都适用。单元测试应该有单一的职责。他们不应该依赖于任何与他们的主要目的不同的东西。
让我们以我的一个文件为例,好吗?
import { SimpleConfiguration } from '../../../../../model/SimpleConfiguration';
import { SessionService } from '../../../session/session.service';
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ConfigurationService } from './configuration.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
describe('ConfigurationService', () => {
let httpMock: HttpTestingController;
let service: ConfigurationService;
const createFakeFile = (fileName: string = 'fileName'): File => {
const blob = new Blob([''], { type: 'text/html' });
blob['lastModifiedDate'] = '';
blob['name'] = fileName;
return <File>blob;
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
HttpClientTestingModule
],
providers: [ConfigurationService, SessionService]
});
httpMock = TestBed.get(HttpTestingController);
service = TestBed.get(ConfigurationService);
});
it('should be created', done => {
expect(service).toBeTruthy();
done();
});
it('getConfigurations should GET on postes-sources/:postID/configuration', (done) => {
service.getConfigurations(0).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
expect(successRequest.request.method).toEqual('GET');
successRequest.flush(null);
httpMock.verify();
});
it('uploadFile should POST on postes-sources/:postID/configuration', (done) => {
service.uploadFile(0, createFakeFile(), new SimpleConfiguration()).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations');
expect(successRequest.request.method).toEqual('POST');
successRequest.flush(null);
httpMock.verify();
});
it('updateComment should POST on postes-sources/:postID/configurations/:confID', (done) => {
service.updateConfiguration(0, 0, new SimpleConfiguration()).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/configurations/0');
expect(successRequest.request.method).toEqual('POST');
successRequest.flush(null);
httpMock.verify();
});
it('getComponentInformations should GET on postes-sources/:postID/tranches/:trancheID/parametres', (done) => {
service.getComponentInformations(0, 0).subscribe(res => done());
const successRequest = httpMock.expectOne(service.URL + 'postes-sources/0/tranches/0/parametres');
expect(successRequest.request.method).toEqual('GET');
successRequest.flush(null);
httpMock.verify();
});
});
让我一步步为您详细解释。
我们从
describe
-ing 我们的测试开始。它允许我们对测试进行分组。在这种情况下,我们按功能对测试进行分组,我们的功能是名为 ConfigurationService 的服务。然后我们提供一个回调函数:Jasmine 将 运行 用于 运行 你的测试的函数。接下来,我们声明我们的变量。在这里,我们声明了 2 个变量和一个函数:
httpMock
、service
和createFakeFile()
。这些变量在整个测试组中都会有帮助,所以我们在顶层声明它们。然后是
beforeEach
:在每次测试之前,这个函数都会运行,做一些事情。在这一个中,它将创建一个 TestBed :这是一些样板代码,将创建某种 Angular 模块,以允许您的测试 运行 就好像您的功能在真实的 Angular 应用程序中。
在这个测试平台,你需要声明你的依赖关系 : 因为我测试的是HTTP服务,所以我必须导入HTTP测试模块,因为我的测试使用路由,我还必须导入路由器测试模块。我还需要导入正在测试的服务,我导入了一个 SessionService
因为我也在我的服务中使用它。
之后,我 通过 TestBed.get
获取那些依赖项的服务实例 :这将允许我监视它们的属性,并查看它们的值以及它们是否'被称为。这也将允许我调用我想要测试的函数。
第一个测试来了。第一个非常简单并且默认实现:我测试服务是否正确创建。如果不是,则意味着您缺少依赖项,或者您错误地模拟了依赖项。
expect(service).toBeTruthy()
是真正的测试:with expect, jasmine expects (duh) 服务是真实的(即它不应该是等于未定义或空)。下一个测试是一个真正的测试:在这个测试中,我希望我的函数调用某个端点,使用某个动词。
我首先要说的是,一旦调用完成,我就必须结束测试。这是通过调用 done
完成的,这是上面给出的回调。
然后,我 模拟 一个 HTTP 调用:这意味着我让 Angular 相信我正在进行 HTTP 调用,但实际上我假装如此。但在它看来,这就像在做一个真实的:这就是模拟:你模拟一个行为或一个值。如您所见,我在特定端点上模拟了一个 HTTP 调用,并且我期望一个特定的 HTTP 动词。
最后,如果我的函数getConfigurations
GETS 到那个URL,我的测试就会成功,否则就会失败。这意味着如果我在实际服务中更改端点,测试将失败:此测试可防止 副作用。
其他的测试也是一样的,我想我就不用解释了。
如果你想知道,我不是一个人做的,就像你一样,我寻求帮助并遵循教程。但是一旦你习惯了它,测试你的功能就会变得非常快速和容易。
希望对您有所帮助,如有任何需要我解释的问题,请随时提出!
我实际上 运行 在过去的一周里才对这个问题感兴趣,所以这对我来说还很新鲜。 Jasmine 也让我有些困惑,所以我可以分享我为解决问题所做的工作。
首先,Angular 教程有点误导新测试人员。它声称我们应该使用 Jasmine,但随后开始使用 TestBed,这有点误导。我最终选择了 TestBed,我可以向您展示我使用的是什么。
所以你有你的描述:
descripe('randomService', () -> {
}
您需要初始化您的`运行domService':
descripe('randomService', () -> {
let randomService: RandomService;
}
使用 beforeEach(),您可以在描述中的每个 it
语句之前重新初始化、赋值等。
descripe('randomService', () -> {
let randomService: RandomService;
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
}
所以我告诉 Angular 在每个 it
块之前重新导入 HttpClientTestingModule
。我的 randomService
需要 HttpClient
所以我需要创建一个 Jasmine Spy 对象,return 是我告诉它的,而不是让我的 randomService
命中实际后端并改变真实我后端的数据。
descripe('randomService', () -> {
let randomService: RandomService;
httpClientSpy;
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
randomService = new RandomService(<any> httpclientSpy);
}
所以现在每当我在 运行domService 中执行 'get' 方法时,它实际上都会使用 httpClientSpy,并且它会编译,因为我告诉 运行domService 我的论点是'any' 类型,据其所知,这实际上是一个真正的 HttpClient,即使它不是。要正确使用它,您必须为您的假 get 设置一个假 return:
descripe('randomService', () -> {
let randomService: RandomService;
httpClientSpy;
mockResponse = { 1: ['first', 'second', 'third'] };
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
randomService = new RandomService(<any> httpclientSpy);
});
it('should return first, second, third', () => {
spyOn(httpClientSpy, 'get').and.returnValue(Observable.of(mockResponse));
// randomService. <your get method here...>
randomService.getValue().subscribe((response) =>
expect(resposne[0].length).toEqual(3);
};
});
那个响应应该是在我们的 beforeEach()
中创建的 mockResponse 它不必在 beforeEach() 中,但在这个例子中我把它留在了那里。