简体   繁体   English

使用AngularJS $ http和Jasmine进行集成测试

[英]Integration test using AngularJS $http and Jasmine

I have an AngularJS service that calls server via $http like this 我有一个AngularJS服务,可以通过$ http这样调用服务器

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

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

and the server returns : 服务器返回:

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

I want to write an integration test using Jasmine that gets response from server and checks its value. 我想使用Jasmine编写一个集成测试,该测试从服务器获取响应并检查其值。 The test file is like: 测试文件如下:

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

and at last my karma.conf.js: 最后是我的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 }); } 

but it always fails as "data" is undefined so what's the problem? 但是它总是失败,因为未定义“数据”,这是什么问题?

angular-mock will not make real ajax calls because it can make unit tests inaccurate. angular-mock不会进行真正的ajax调用,因为它会使单元测试不准确。 In order to let Angular call the web service and still be able to maintain a good test, I recommend to use ngMidwayTester . 为了让Angular调用Web服务并且仍然能够保持良好的测试,我建议使用ngMidwayTester Combining it with Jasmine's async support ( done() function), you can perform your test. 将其与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!');
            });
    });
});

Explanation 说明

You are setting data inside your async callback function, but evaluating it outside. 您正在内部异步回调函数中设置data ,但在外部进行评估。 Imagine your async call takes 3 seconds... This would be its lifecycle: 假设您的异步呼叫需要3秒钟,这就是它的生命周期:

  1. getCourses is called getCourses被称为
  2. data is undefined and evaluation fails 数据不确定,评估失败
  3. your test finishes 您的测试完成
  4. ... after 3 seconds... getCourses callback and set it to new value ... 3秒后... getCourses回调并将其设置为新值

Proposal 提案

If you want to test an asyncrhonous call with jasmine as you have in courseService.getCourses() you need to notice it to your test using the parameter done . 如果要像在courseService.getCourses()一样使用jasmine测试异步调用,则需要使用done参数将它注意到在测试中。

From jasmine documentation - Asynchronous support : jasmine文档-异步支持

This spec will not start until the done function is called in the call to beforeEach above. 直到在上述对beforeEach的调用中调用完函数,该规范才会开始。 And this spec will not complete until its done is called. 并且,只有在完成之前,此规范才能完成。

So basic implementation, as you can see at docu, is: 因此,如您在docu上看到的,基本实现是:

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

In addition, your expects need to be evaluate after receiving the callback from the async, so introduce them in your then callback function. 另外,您的期望需要在接收到来自异步的回调之后进行评估,因此请将它们引入then回调函数中。 So, assuming your service injection is correct, you should have something like: 因此,假设您的服务注入正确无误,则应具有以下内容:

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

      });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM