使用 AngularJS $http 和 Jasmine 的集成测试

Integration test using AngularJS $http and Jasmine

我有一个 AngularJS 服务,它像这样通过 $http 调用服务器

function DefineCourseService($http) {
  var service = {
    getCourses: getCourses
  };

  function getCourses(id) {
    return $http({
      url: '/api/Course',
      method: 'GET'
    });
  }
}

和服务器 returns :

[{Code:'123',Title:'Test'}]

我想使用 Jasmine 编写一个集成测试,从服务器获取响应并检查其值。测试文件如下:

(function() {
  'use strict';
  define(['angular-mocks', 'defineCourse.service'], function() {
    describe("Course service", function() {
      var courseService, data, deferredResolution, parentScope;

      beforeEach(function() {
        module('modabber.services');

      });

      beforeEach(inject(function($q, $rootScope, DefineCourseService) {
        courseService = DefineCourseService;
        deferredResolution = $q.defer();
        parentScope = $rootScope;
      }));

      it("get courses", function() {
        spyOn(courseService, 'getCourses').and.callThrough();
        deferredResolution.resolve();
        courseService.getCourses().then(function(result) {
          data = result;
        });
        expect(courseService.getCourses).toHaveBeenCalled();
        expect(data).toBeUndefined();

        parentScope.$digest();
        expect(data).toBeDefined();
        done();
      });
    });
  });
})();

最后是我的 karma.conf.js:

module.exports = function(config) {
  config.set({
    basePath: '../',
    frameworks: ['jasmine', 'requirejs'],
    files: [
      'karma/test-main.js', {
        pattern: 'WebApiControllers/**/*.js',
        included: false
      }, {
        pattern: 'scripts/vendor/*.js',
        included: false
      }, {
        pattern: 'bower_components/ngMidwayTester/src/ngMidwayTester.js',
        included: false
      }, {
        pattern: 'bower_components/**/*.min.js',
        included: false
      }, {
        pattern: 'scripts/*.js',
        included: false
      }, {
        pattern: 'app/services/*.js',
        included: false
      }, {
        pattern: 'app/directives/*.js',
        included: false
      },

    ],
    exclude: ['scripts/main.js'],
    preprocessors: {    },
    reporters: ['progress'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false
  });
}

但它总是失败,因为 "data" 未定义,这是什么问题?

说明

您在异步回调函数内部设置 data,但在外部对其进行评估。 想象一下您的异步调用需要 3 秒......这将是它的生命周期:

  1. getCourses 被调用
  2. 数据未定义且评估失败
  3. 你的测试完成
  4. ... 3 秒后...getCourses 回调并将其设置为新值

提案

如果您想像在 courseService.getCourses() 中那样使用 jasmine 测试异步调用,您需要使用参数 done.

在测试中注意它

来自jasmine documentation - Asynchronous support

This spec will not start until the done function is called in the call to beforeEach above. And this spec will not complete until its done is called.

正如您在 docu 上看到的那样,基本实现是:

it("takes a long time", function(done) { //done as a parameter
      setTimeout(function() {     
        done();                           //done is resolve to finish your test
      }, 9000);
    });

此外,您的期望需要在收到异步回调后进行评估,因此请在您的 then 回调函数中引入它们。 所以,假设你的服务注入是正确的,你应该有这样的东西:

it("get courses", function(done) { //Add done as parameter
        // spyOn(courseService, 'getCourses').and.callThrough();Now this spyOn makes no sense.
        deferredResolution.resolve();
        courseService.getCourses().then(function(result) {
          data = result;
          // expect(courseService.getCourses).toHaveBeenCalled(); Not needed
          // expect(data).toBeUndefined();  I assume you are expecting it defined
          // parentScope.$digest(); If you are only evaluating result and it's not change by the $scope, you don't need a $digest.
          expect(data).toBeDefined();
          done();
        }, function(){
           //If your async call fails and done() is never called, 
           // your test will fail with its timeout...
           //... Or you can force a test error
           expect(1).toBe(2);
           done();
        });

      });

angular-mock 不会进行真正的 ajax 调用,因为它会使单元测试不准确。为了让Angular调用web服务的同时还能保持良好的测试,我推荐使用ngMidwayTester。结合 Jasmine 的异步支持(done() 函数),您可以执行测试。

describe('Course service', function() {
    var tester;
    var courseService;

    beforeEach(function() {
        tester = ngMidwayTester('modabber.services');
        courseService = tester.inject('DefineCourseService');
    });

    afterEach(function() {
        tester.destroy();
        tester = null;
    });

    it('get courses', function(done) {
        courseService.getCourses()
            .then(function(result) {
                expect(result).toBeDefined();
                expect(result.Code).toBe('123');
                done();
            }, function() {
                done.fail('Web service call failed!');
            });
    });
});