typescript 使用 jasmine /karma 进行 Angular 4 单元测试和 http post mocking - 如何修复

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46420640/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:55:40  来源:igfitidea点击:

Angular 4 unit testing with jasmine /karma with http post mocking - how to fix

angularunit-testingtypescriptjasminekarma-jasmine

提问by

I have a service I want to unit test in angular 4 typescript jasmine.

我有一项服务,我想在 angular 4 typescript jasmine 中进行单元测试。

Now, the httpis doing a post, and it returns an identity, however.. it is not sending anything.

现在,http正在执行 a post,并且它返回一个身份,但是......它没有发送任何东西。

I want to just have good code coverage but i don't understand how to quite complete this mocking statement.

我只想拥有良好的代码覆盖率,但我不明白如何完全完成这个嘲讽语句。

here is the method for http post in my service file

这是我的服务文件中 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());

}

Then the SPEC FILE, which i don't get what to really test, i suppose faking that i received a number back from the service http post, the response should be something like 000000014

然后是 SPEC FILE,我不知道要真正测试什么,我想我假装从服务 http 帖子收到了一个号码,响应应该是这样的 000000014

Spec

规格

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?

        })

    })

})

采纳答案by Sonicd300

In order to achieve what you want, the mock you need is a simple function that returns the same as the POST would do normally; another thing is your test should not hit the server for real, so you would need something like this (you might need to add other dependencies):

为了实现你想要的,你需要的模拟是一个简单的函数,它返回与 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');
    });
  });
});

回答by Tabueeee

Well the way you did setup the test/mock you can fake the return of the post call and check that you got the result you expected. By doing so you will test that the mocked response will be properly converted by your map statement. With your spy you can also check how the post method was called. This will check if the options match what you expect.

那么你设置测试/模拟的方式你可以伪造post调用的返回并检查你是否得到了你期望的结果。通过这样做,您将测试模拟响应是否会被您的 map 语句正确转换。通过您的间谍,您还可以检查 post 方法是如何被调用的。这将检查选项是否符合您的预期。

But in my opinion that's a rather complicated solution. I'd prefer to avoid mocks and spies by splitting the method so every method is just doing one thing. Because your addSession method is currently doing three different (yet logically dependent) things:

但在我看来,这是一个相当复杂的解决方案。我更愿意通过拆分方法来避免模拟和间谍,因此每种方法都只做一件事。因为您的 addSession 方法目前正在做三件不同(但逻辑上依赖)的事情:

  1. create options for an addSession xhr call
  2. performs the call
  3. convert the response
  1. 为 addSession xhr 调用创建选项
  2. 执行呼叫
  3. 转换响应

If you break the method up in three you can easily test method #1 and #3 in separate tests and method #2 would only contain the call to the http library. This allows you to achieve the same test value as above without calling the http library.

如果将方法分成三部分,则可以轻松地在单独的测试中测试方法 #1 和 #3,而方法 #2 将仅包含对 http 库的调用。这允许您在不调用 http 库的情况下实现与上述相同的测试值。

Now what about method #2... it's still untested and in my opinion there is no reason to test it at all. Because you did not write that code. Also if you are using angulars http module I am sure they have solid unit tests themselves.

现在方法#2 怎么样...它仍然未经测试,在我看来根本没有理由测试它。因为你没有写那个代码。此外,如果您使用的是 angulars http 模块,我相信他们自己有可靠的单元测试。

The response of your service should already be covered by an additional integration test, run less frequently checking the service api will still return what you expect.

你的服务的响应应该已经被一个额外的集成测试覆盖了,不那么频繁地检查服务 api 仍然会返回你期望的结果。

If you really want that one line green in your code coverage, then you could optionally use a library called nock. Nock will intercept all xhr traffic your app will cause. In your test file you can map xhr requests to mocked responses with the nock object.

如果您真的希望代码覆盖率中有一行绿色,那么您可以选择使用名为 nock 的库。Nock 将拦截您的应用程序将导致的所有 xhr 流量。在您的测试文件中,您可以使用 nock 对象将 xhr 请求映射到模拟响应。

var scope = nock('http://myapp.iriscouch.com')
                .post('/users', {
                  username: 'pgte',
                  email: '[email protected]'
                })
                .reply(201, {
                  ok: true,
                  id: '123ABC',
                  rev: '946B7D1C'
                });

copied from: https://www.npmjs.com/package/nock

复制自:https: //www.npmjs.com/package/nock

For reference and additional information about testing in general and how much to test i recommend watching "Budgeting Reality" by Justin Searls

有关一般测试以及测试量的参考和其他信息,我建议您观看 Justin Searls 的“Budgeting Reality”

回答by TekTimmy

With Angular 4.3 came the HttpClient service which replaces Http and provides an easier way to mock HTTP requests. It's well documented on the official page: https://angular.io/guide/http

Angular 4.3 带来了 HttpClient 服务,它取代了 Http 并提供了一种更简单的模拟 HTTP 请求的方法。它在官方页面上有详细记录:https: //angular.io/guide/http

回答by AnilKumar

Sample test case for http service requests

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: '[email protected]' };

            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: '[email protected]' };

            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);
        });

    });

});