我正在学习单元测试和Angular,所以我是初学者。 我已经提到了几个关于单元测试http的角度的教程和文章。
我无法理解httpMock使用HttpTestingController做了什么。我们称实际服务的功能然后我们称之为模拟? 什么是潜在的过程? 请参考一些文章以便更好地理解。
提前致谢。
修改 这是issue,我坚持使用httpMock。
答案 0 :(得分:4)
我们以我的档案为例,好吗?
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
开始我们的测试。它允许我们对测试进行分组。在这种情况下,我们按功能对测试进行分组,我们的功能是名为 ConfigurationService 的服务。然后我们提供一个回调函数:这是由Jasmine运行以运行测试的函数。
接下来,我们声明我们的变量。在这里,我们声明了2个变量和一个函数:httpMock
,service
和createFakeFile()
。这些变量在整个测试组中都会有所帮助,因此我们将它们声明为顶层。
然后是beforeEach
:在每次测试之前,都会运行此函数来执行某些操作。在这一个中,它将创建一个 TestBed :这是一些样板代码,它将创建某种Angular模块,以允许您的测试运行,就像您的功能在真正的Angular应用程序中一样。
在此测试台中,您需要声明依赖项:因为我正在测试HTTP服务,所以我必须导入HTTP测试模块,并且因为我的测试使用路由,所以还必须导入路由器测试模块。我也需要导入正在测试的服务,并导入SessionService
,因为我也在我的服务中使用它。
之后,我通过TestBed.get
获取这些依赖项的服务实例:这将允许我监视其属性,并查看其值以及是否已调用它们。这也允许我调用我想测试的函数。
首次测试。第一个非常简单,默认情况下实现:我测试服务是否正确创建。如果没有,则意味着您缺少依赖关系,或者您嘲笑您的依赖关系错误。 expect(service).toBeTruthy()
是真正的考验:期待,jasmine期望(duh)服务是真实的(即它不应该等于undefined或null) 。
下一个测试是一个真正的测试:在这个测试期间,我希望我的函数能够使用某个动词来调用某个端点。
我首先说,一旦拨打电话,我必须结束测试。这是通过调用done
来完成的,这是上面给出的回调。
然后,我模拟一个HTTP调用:这意味着我让Angular相信我正在进行HTTP调用,但我实际上是假装。但在它看来,它就像制作一个真实的:这就是模拟:你模拟行为或价值。如您所见,我在特定端点上模拟HTTP调用,并且我期望一个特定的HTTP谓词。
最后,如果我的函数getConfigurations
GETS在该URL上,我的测试将成功,如果没有,它将失败。这意味着如果我在实际服务中更改了端点,则测试将失败:此测试可防止副作用。
其他测试与此相同,所以我不需要解释它们。
如果你想知道,我没有单独完成,就像你我寻求帮助并遵循教程一样。但是一旦你习惯它,它就会变得非常快速和容易地测试你的功能。
我希望这会有所帮助,并随时提出您想要我解释的任何问题!
答案 1 :(得分:1)
模拟服务的想法是您不需要使用实际功能(真正的http调用),但您确实需要服务方法来运行您的代码而没有任何例外
例如: 你有一个组件,它在某些时候通过Http从某些API收集数据。 其中一个单元测试应该测试你的组件是否打了那个电话,但是如果那里有一个真正的电话你就不会给出一个该死的。单元测试的重点是测试呼叫是否已经发生。
编辑: 如果某些内部逻辑进行调用以收集数据以显示某些内容,则会发生同样的情况。你应该模拟http调用并返回你自己的数据。你的单元测试不应该来自外部。想象一下,您的测试将在没有互联网的环境中运行。测试应该随时通过。
无论您测试哪种服务,此方案都适用。单元测试应该具有单一的责任感。他们不应该依赖于与他们的主要目的不同的东西。
答案 2 :(得分:1)
我实际上刚刚在过去一周遇到过这个问题,所以这在我脑海中非常新鲜。 Jasmine对我有点困惑,所以我可以分享我为解决问题所做的工作。
首先,Angular教程会误导新测试人员。它声称我们应该使用Jasmine,但后来开始使用TestBed,这有点误导。我最终选择了TestBed,我可以告诉你我用过的东西。
所以你有你的描述:
descripe('randomService', () -> {
}
您需要初始化`randomService':
descripe('randomService', () -> {
let randomService: RandomService;
}
使用beforeEach(),您可以在描述中的每个it
语句之前重新初始化,赋值等。
descripe('randomService', () -> {
let randomService: RandomService;
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
}
所以我告诉Angular在每个HttpClientTestingModule
块之前重新导入it
。我的randomService
需要HttpClient
所以我需要创建一个Jasmine Spy对象,它返回我告诉它的内容,而不是让我的randomService
命中实际的后端并改变后端的实际数据。
descripe('randomService', () -> {
let randomService: RandomService;
httpClientSpy;
beforeEach(() => {
imports: [
HttpClientTestingModule
]
});
httpClientSpy = jasmine.CreateSpyObj('Http', ['get']);
randomService = new RandomService(<any> httpclientSpy);
}
所以现在每当我在randomService中使用'get'方法时,它就会真正使用httpClientSpy,并且它会编译,因为我告诉randomService我的参数类型为'any',并且它的'最佳知识,即实际上是一个真正的HttpClient,即使它不是。要正确使用此功能,您必须为假冒产品设置假回报:
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()中,但在此示例中,我将其保留在那里。