使用 jasmine / karma 进行 Angular 4 单元测试和 http post mocking - 如何修复
Posted
技术标签:
【中文标题】使用 jasmine / karma 进行 Angular 4 单元测试和 http post mocking - 如何修复【英文标题】:Angular 4 unit testing with jasmine /karma with http post mocking - how to fix 【发布时间】:2018-03-07 08:01:12 【问题描述】:我有一项服务,我想在 Angular 4 typescript jasmine 中进行单元测试。
现在,http
正在执行 post
,它返回一个身份,但是......它没有发送任何东西。
我只想拥有良好的代码覆盖率,但我不明白如何完全完成这个模拟语句。
这是我的服务文件中http post的方法
addSession()
let headers = new Headers( 'Content-Type': 'application/json' );
let options = new RequestOptions( headers: headers );
return this.http.post(this.url, JSON.stringify(), options)
.map((response: Response) => response.json());
然后是 SPEC FILE ,我没有得到真正测试的内容,我想假装我从服务 http 帖子中收到了一个数字,响应应该类似于 000000014
规格
import TrackerFormService from './tracker-form.service'
import Observable from 'rxjs/Observable'
describe('TrackerFormService', () =>
let trackerFormService: TrackerFormService,
mockHttp;
beforeEach(() =>
mockHttp = jasmine.createSpyObj('mockHttp', ['get', 'post', 'put']
)
trackerFormService = new TrackerFormService(mockHttp);
);
describe('addSession', () =>
it('add session ', () =>
// how to test, what to test?
// response , is a number? how to mock/fake this?
)
)
)
【问题讨论】:
【参考方案1】:Angular 4.3 出现了 HttpClient 服务,它取代了 Http 并提供了一种更简单的方法来模拟 HTTP 请求。官方页面上有详细记录:https://angular.io/guide/http
【讨论】:
我已经在使用另一个,我在问如何专门模拟我拥有的东西。您所说的相当于 1. in .net core 或 c# 7 ,您现在可以做 XYZ,非常好或 2. 在 ES6 中,现在好多了... 如果我做对了,你想在 TrackerFormService 的测试中模拟 TrackerFormService.addSession() 方法,这在我看来没有意义,因为你想测试真正的实现而不是模拟。 它从响应中返回一个类似 000000015 的数字,所以我确信人们已经嘲笑/伪造了 http post 服务的过程。我可以找到示例,但它们非常具体,以至于试图弄清楚如何模拟/伪造我的特定测试是我想要的。我不声称知道该怎么做,所以我在问这个问题【参考方案2】:http 服务请求的示例测试用例
describe('Forgot Password Controller', function ()
var $controller,
$httpBackend,
$q,
$rootScope,
$state,
controller,
scope,
accountProvider;
beforeEach(module('app'));
beforeEach(inject(function (_$injector_, _$controller_, _$rootScope_)
$controller = _$controller_;
$rootScope = _$rootScope_;
$httpBackend = _$injector_.get('$httpBackend');
$state = _$injector_.get('$state');
$q = _$injector_.get('$q');
accountProvider = _$injector_.get('accountProvider');
scope = _$rootScope_.$new();
controller = $controller(app.controllers.forgotPassword,
$state: $state,
accountProvider: accountProvider
);
));
afterEach(function ()
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.verifyNoOutstandingExpectation();
);
describe('forgot password submission', function ()
it('Can submit a forgot password request successfully', function ()
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(200);
spyOn($state, 'go');
controller.form = emailAddress: 'aks@gmail.com' ;
controller.submit();
expect(controller.submitting).toBe(true);
$httpBackend.flush();
expect(controller.submitting).toBe(false);
expect($state.go).toHaveBeenCalledWith('login', successMessage: 'An email sent to ' + controller.form.emailAddress + ' contains instructions for resetting your password.' );
);
it('Can handle when a user is not found when submitting a forgot password request', function ()
$httpBackend.expectPOST(app.env.EDGE_SERVICE_PATH + '/events/requestPasswordReset').respond(404);
spyOn($state, 'go');
controller.form = emailAddress: 'aks@gmail.com' ;
controller.submit();
expect(controller.submitting).toBe(true);
$httpBackend.flush();
// We intentionally want to make it appear to the user that the password reset email was sent even when a user
// does not exist, to help hide info about which users exist in the system
expect(controller.submitting).toBe(false);
expect($state.go).toHaveBeenCalledWith('login', successMessage: 'An email sent to ' + controller.form.emailAddress + ' contains instructions for resetting your password.' );
);
it('Can handle unexpected errors from submitting a forgot password request', function ()
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(500);
controller.submit();
$httpBackend.flush();
expect(controller.errors.unexpectedError).toBe(true);
);
it('Can handle 422 validation errors from submitting a forgot password request', function ()
var responseData =
fieldErrors:
username: [code: 'error', code: 'required', message: 'This is required.']
;
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(422, responseData);
controller.submit();
$httpBackend.flush();
expect(controller.errors.validationErrors).toBe(true);
expect(controller.errors.fieldErrors).toEqual(responseData.fieldErrors);
);
it('Can handle 503 service unavailable from submitting a forgot password request', function ()
$httpBackend.expectPOST("URL DOMAIN" + '/events/requestPasswordReset').respond(503);
controller.submit();
$httpBackend.flush();
expect(controller.errors.serviceUnavailable).toBe(true);
);
);
);
【讨论】:
这不是 angular 4 解决方案。 是的,我在这里提供有关如何为rest apis编写测试用例的想法。 这将如何帮助在 ng 中模拟服务??【参考方案3】:为了实现你想要的,你需要的 mock 是一个简单的函数,它返回的结果与 POST 正常的返回值相同;另一件事是你的测试不应该真正到达服务器,所以你需要这样的东西(你可能需要添加其他依赖项):
import HttpModule from '@angular/http';
import TrackerFormService from './tracker-form.service'
import Observable from 'rxjs/Observable'
describe('TrackerFormService', () =>
// Mock the service like this and add all the functions you have in this fashion
let trackerFormService: TrackerFormService,
mockService =
addSession: jasmine.createSpy('addSession').and.returnValue(Observable.of('your session object mock goes here'))
;
beforeEach(() =>
TestBed.configureTestingModule(
imports: [HttpModule],
providers: [
provide: TrackerFormService,
useValue: mockService
]
);
);
// Do this trick to inject the service every time, and just use `service` in your tests
beforeEach(inject([TrackerFormService], (trackerFormService) =>
service = trackerFormService;
));
describe('addSession', () =>
it('add session ', () =>
let fakeResponse = null;
// Call the service function and subscribe to it to catch the fake response coming from the mock.
service.addSession().subscribe((value) =>
// in here value will be whatever you put as returnValue (remember to keep the observable.of())
fakeResponse = value;
);
// expects as in any test.
expect(fakeResponse).toBeDefined();
expect(fakeResponse).toBe('your session object mock goes here');
);
);
);
【讨论】:
【参考方案4】:好吧,您设置测试/模拟的方式可以伪造 post call 的返回并检查您是否得到了预期的结果。通过这样做,您将测试模拟的响应是否会被您的 map 语句正确转换。通过您的间谍,您还可以检查 post 方法是如何被调用的。这将检查选项是否符合您的预期。
但在我看来,这是一个相当复杂的解决方案。我宁愿通过拆分方法来避免模拟和间谍,所以每个方法都只是做一件事。因为您的 addSession 方法当前正在执行三个不同(但在逻辑上依赖)的事情:
-
为 addSession xhr 调用创建选项
执行呼叫
转换响应
如果您将方法分成三部分,您可以轻松地在单独的测试中测试方法 #1 和 #3,而方法 #2 将只包含对 http 库的调用。这允许您在不调用 http 库的情况下获得与上述相同的测试值。
现在方法#2 怎么样......它仍然未经测试,我认为根本没有理由对其进行测试。因为您没有编写该代码。此外,如果您使用 angulars http 模块,我相信他们自己也有可靠的单元测试。
您的服务的响应应该已经被额外的集成测试覆盖,运行频率较低,检查服务 api 是否仍会返回您期望的内容。
如果您真的希望代码覆盖中的那一行是绿色的,那么您可以选择使用一个名为 nock 的库。 Nock 将拦截您的应用程序将导致的所有 xhr 流量。在您的测试文件中,您可以使用 nock 对象将 xhr 请求映射到模拟响应。
var scope = nock('http://myapp.iriscouch.com')
.post('/users',
username: 'pgte',
email: 'pedro.teixeira@gmail.com'
)
.reply(201,
ok: true,
id: '123ABC',
rev: '946B7D1C'
);
复制自:https://www.npmjs.com/package/nock
有关一般测试以及测试量的参考和其他信息,我建议观看 Justin Searls 的“预算现实”
【讨论】:
以上是关于使用 jasmine / karma 进行 Angular 4 单元测试和 http post mocking - 如何修复的主要内容,如果未能解决你的问题,请参考以下文章
markdown 使用Karma和Jasmine进行测试 - 介绍
使用 Angular 和 Jasmine/Karma 的私有方法进行测试和代码覆盖
使用 jasmine 和 karma 进行单元测试时形成数组错误
使用 Jasmine 和 Karma 进行角度单元测试时出错
为啥使用 jasmine 对 Node typescript 项目进行 Karma 单元测试会显示包含依赖项的覆盖范围?