使用ngMock在服务单元测试中模拟$ http调用

5 unit-testing jasmine angularjs

我一直在看这个好几个小时.我在例子后尝试了一些例子.我似乎无法让这个工作.请帮忙=)

我正在做一个关于angular-seed repo的干净克隆(git://github.com/angular/angular-seed.git).除了下面列出的内容外,我没有做任何更改.

问题:

当我运行以下操作时,测试工作正常.请注意,在此版本中,服务在执行任何类型的$ http调用之前返回一个值.

./app/js/services/services.js

'use strict';
angular.module('myApp.services', [])
  .factory("exampleService", function ($http) {
    return {value:"goodValue"};

    $http.get("/exampleUrl")
      .success(function () {
        return {value:"goodValue"};
      })
      .error(function () {
        return {value:"badValue"};
      })
  });
Run Code Online (Sandbox Code Playgroud)

./test/unit/servicesSpec.js

'use strict';
describe('service', function() {
  var $httpBackend;

  beforeEach(module('myApp.services'));

  beforeEach(inject(function ($injector) {
    $httpBackend = $injector.get("$httpBackend");
    $httpBackend.when("GET", "/exampleUrl")
      .respond({value:"goodValue"});
  }));

  afterEach(function () {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  describe('exampleService', function () {
    it('.value should be "goodValue"', inject(function (exampleService) {
      expect(exampleService.value).toEqual("goodValue");
    }));
  });
});
Run Code Online (Sandbox Code Playgroud)

结果

info (watcher): Changed file "/home/username/mocktest/test/unit/servicesSpec.js".
Chrome 26.0: Executed 5 of 5 SUCCESS (0.136 secs / 0.035 secs)
Run Code Online (Sandbox Code Playgroud)

当我删除该行return {value:"goodValue"};并实际让它运行时$http.get(),整个事情就会出现以下错误:

info (watcher): Changed file "/home/username/mocktest/app/js/services.js".
Chrome 26.0 service exampleService .value should be "goodValue" FAILED
  TypeError: Cannot read property 'value' of undefined
      at null.<anonymous> (/home/username/mocktest/test/unit/servicesSpec.js:22:28)
      at Object.invoke (/home/username/mocktest/app/lib/angular/angular.js:2864:28)
      at workFn (/home/username/mocktest/test/lib/angular/angular-mocks.js:1758:20)
  Error: Declaration Location
      at window.jasmine.window.inject.angular.mock.inject (/home/username/mocktest/test/lib/angular/angular-mocks.js:1744:25)
      at null.<anonymous> (/home/username/mocktest/test/unit/servicesSpec.js:21:40)
      at null.<anonymous> (/home/username/mocktest/test/unit/servicesSpec.js:20:3)
      at /home/username/mocktest/test/unit/servicesSpec.js:4:1
  Error: Unflushed requests: 1
      at Error (<anonymous>)
      at Function.$httpBackend.verifyNoOutstandingRequest (/home/username/mocktest/test/lib/angular/angular-mocks.js:1225:13)
      at null.<anonymous> (/home/username/mocktest/test/unit/servicesSpec.js:17:18)
Chrome 26.0: Executed 5 of 5 (1 FAILED) (0.14 secs / 0.043 secs)
Run Code Online (Sandbox Code Playgroud)

思考

怀疑我需要在服务中做一些promise对象的返回,然后再解决它,但我不知道那会是什么.任何帮助赞赏.

小智 12

您怀疑的解决方案是正确的 - 承诺是修复错误/失败测试的关键.

使用原始$http服务有条件地返回工厂对象不起作用,因为您实际上没有从工厂函数返回任何内容(由于异步承诺解析$http).

我考虑过把return你的原始$http.get()电话放在前面.但这不是理想的行为,因为AngularJS .factory方法应返回您定义的服务对象,而不仅仅是调用返回的承诺$http.

解决方案是在您的exampleService对象上公开一个可以由您的应用程序调用的方法.该getData()方法返回一个promise(via $http),您的app/test可以使用.success()或者异步处理.error()

./app/js/services/services.js

'use strict';
angular.module('myApp.services', [])
    .factory("exampleService", function ($http) {
        return {
            getData: function () {
                return $http.get("/exampleUrl");
            }
        }
  });
Run Code Online (Sandbox Code Playgroud)

./test/unit/servicesSpec.js

'use strict';
describe('service', function() {
  var $httpBackend;

  beforeEach(module('myApp.services'));

  beforeEach(inject(function ($injector) {
    $httpBackend = $injector.get("$httpBackend");
    $httpBackend.when("GET", "/exampleUrl")
        .respond(200, {value:"goodValue"});
  }));

  afterEach(function () {
    $httpBackend.flush()
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  describe('exampleService successful http request', function () {
    it('.value should be "goodValue"', inject(function (exampleService) {

        exampleService.getData().success(function(response) {
          expect(response.value).toEqual("goodValue");
        }).error( function(response) {
          //should not error with $httpBackend interceptor 200 status
          expect(false).toEqual(true);
        });

    }));
  });


});
Run Code Online (Sandbox Code Playgroud)

请注意,Destron的评论对于$httpBackend.flush()让模拟后端拦截$http.get()来自您服务的请求也很重要.

希望有所帮助.