Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
402 views
in Technique[技术] by (71.8m points)

javascript - Making real requests to HTTP server in AngularJS unit/integration tests

Making a request that wasn't mocked with $httpBackend.when in Angular 1.x unit/integration test results in an error:

Error: Unexpected request: GET /real-request

Is it possible to make real HTTP requests with ngMock and Karma+Jasmine test rig? What is a good practice to do that?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

AngularJS is opinionated framework, and its opinion on HTTP requests in unit tests is that all of them should be mocked.

It is not advisable to do real HTTP requests in unit tests for two reasons. Unit tests are supposed to be isolated and be fast. Making a real request makes a test asynchronous, which slows down test runs significantly. Making a real request breaks the isolation, the fact if a test passes depends on both tested unit and a backend.

This was taken into consideration when AngularJS ngMock module was designed (it is loaded automatically in unit tests by angular-mocks.js). The developer will hardly ever do asynchronous Jasmine unit tests with Angular, because there's no need to do that.

Integration tests differ. They may be not as broad as E2E tests (which are often run by Protractor) and test how several units work together, this may include a backend (HTTP server). So in the end Karma and Jasmine are still used, but the tests may be slower and asynchronous and do real HTTP requests.

This is where ngMockE2E module (usually used in E2E tests) kicks in. It is included in angular-mocks.js alongside with ngMock but isn't loaded by default.

The ngMockE2E is an AngularJS module which contains mocks suitable for end-to-end testing. Currently there is only one mock present in this module - the e2e $httpBackend mock.

ngMockE2E contains different $httpBackend implementation which can be used for the purpose. Its API varies. It isn't supposed to use flush and extend methods. $rootScope.$digest() may be used if there are $q promise chains that should be executed.

ngMockE2E won't work out of the box properly because of the the adjustments that are being made to Angular services by ngMock when its helper functions module and inject are used. A helper module for intergration tests can be used instead:

angular.module('ngMockI9n', []).config(function ($provide) {
  // hack to restore original implementations which were overridden by ngMock
  angular.injector(['ng', function ($httpBackendProvider, $browserProvider) {
    $provide.provider('$httpBackend', $httpBackendProvider);
    $provide.provider('$browserI9n', $browserProvider);
  }]);

  // make ngMockE2E $httpBackend use original $browser
  var httpBackendI9nDecorator = angular.mock.e2e.$httpBackendDecorator
  .map(function (dep) {
    return (dep === '$browser') ? '$browserI9n' : dep;
  });
  $provide.decorator('$httpBackend', httpBackendI9nDecorator);
});

Additionally, a recipe for whitelisted real HTTP requests can be used to make the testing easier, although the best practice is to enumerate real and mocked requests explicitly.

beforeEach(module('app'));  
beforeEach(module('ngMockI9n'));

beforeEach(inject(function ($httpBackend) {
  $httpBackend.when('GET', '/mocked-request').respond(200, {});

  // all other requests will be automatically whitelisted and treated as real
  // so make sure that mocked requests are mocked above this point
  angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD', 'PUT', 'POST', 'PATCH'],
  function (method) {
    $httpBackend.when(method).passThrough();
  });
}));


it('does real async request', function (done) {
  // async tests need extra `done` param
  inject(function () {
    $http.get('real-request').then(function (response) {
      expect(response.data).toEqual(...);
    })
    .then(done, done.fail);

    $rootScope.$digest();
  });
});

it('does mocked sync request', function (done) {
  // tests with mocked requests are async, too
  inject(function () {
    $http.get('mocked-request').then(function (response) {
      expect(response.data).toEqual(...);
    })
    .then(done, done.fail);

    $rootScope.$digest();
  });
});

TL;DR: Use $httpBackend from ngMockE2E in integration tests for real requests, this requires some extra work to make it compatible with ngMock. Never do real requests in unit tests, this results in slow and trashy tests.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...