如何在使用angular-datatables时创建一个jasmine单元测试

3

我正在项目中使用angular-datatables,我想为它编写Jasmine/Karma单元测试。

这是我的控制器代码:

 $scope.dtOptions = DTOptionsBuilder.fromSource('/api/books/')
                .withBootstrap()
                .withPaginationType('simple_numbers')
                .withDisplayLength(10)
                //.withOption('serverSide', true)
                .withOption('processing', true)
                .withOption('createdRow', createdRow);

            $scope.dtColumns = [
                DTColumnBuilder.newColumn('id').withTitle('ID'),
                DTColumnBuilder.newColumn('name').withTitle('Name')
                DTColumnBuilder.newColumn(null).withTitle('Actions').notSortable()
                    .renderWith(actionsHtml)
            ];

我现在该如何编写单元测试,模拟一个来自/api/books的JSON响应?
2个回答

1
var $scope, $state, DTColumnBuilder, DTOptionsBuilder, createController, $httpBackend;

beforeEach(function () {
  DTColumnBuilder  = {};
  DTOptionsBuilder = {};
  $state           = {};
  $httpBackend     = {};

  module('app', function ($provide) {
    $provide.value('$state', $state);
    $provide.value('$httpBackend', $httpBackend);
    $provide.value('DTColumnBuilder', DTColumnBuilder);
    $provide.value('DTOptionsBuilder', DTOptionsBuilder);
  });

  inject(function ($controller, $injector) {
    $scope = $injector.get('$rootScope').$new();
    $state = $injector.get('$state');
    $httpBackend = $injector.get('$httpBackend');
    DTColumnBuilder  = $injector.get('DTColumnBuilder');
    DTOptionsBuilder = $injector.get('DTOptionsBuilder');

    aliasOfYourController = function () {
      return $controller('originalNameOfController', {
        $scope: scope,
        $state: $state,
        DTOptionsBuilder: DTOptionsBuilder,
        DTColumnBuilder: DTColumnBuilder        
      });
    }

    spyOn($state, 'go');

    $httpBackend.flush();
  });

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

  // Stub out the methods of interest. 
  DTOptionsBuilder.fromSource = angular.noop;
  DTColumnBuilder.bar = function () { return 'bar'; };
});

间谍的本质是让原始实现发挥作用,但记录所调用的所有函数及相关数据。Stub则是带有扩展API的spy,您可以完全修改该函数的工作方式,包括返回值、预期参数等等。假设我们使用前面提到的beforeEach块,此时DTOptionsBuilder.fromSource将是一个noop。因此,安全地在其上进行spy并期望已调用该方法。
it('should have been called', function () {
  var spy = spyOn(DTOPtionsBuilder, 'fromSource');
  aliasOfYourController();
  expect(spy).toHaveBeenCalled();
});

如果你想要操作该函数的返回值,我建议使用sinonjs并将其设置为stub
it('became "foo"', function () {
  DTOptionsBuilder.fromSource = sinon.stub().returns('foo');
  aliasOfYourController();
  expect($scope.dtOptions).toEqual('foo');
});

现在,由于您正在使用promise,所以有点复杂,但是模拟一个基于promise的函数的基础是:
  • $q注入到您的规范文件中。
  • 告诉stub已解决的promise的情况下返回$q.when(/** value **/)
  • 告诉stub被拒绝的promise的情况下返回$q.reject(/** err **/)
  • 运行$timeout.flush()来刷新所有延迟任务。如果您正在模拟http响应,则在单元测试中使用$httpBackend,并使用$httpBackend.flush()刷新所有延迟任务。
  • 触发done回调以通知Jasmine您已完成等待异步任务(可能不需要)。这取决于测试框架/运行器。
可能看起来像这样:
it('resolves with "foo"', function (done) {
  DTOptionsBuilder.fromSource = sinon.stub().returns($q.when('foo'));
  expect($scope.options).to.eventually.become('foo').and.notify(done); // this is taken from the chai-as-promised library, I'm not sure what the Jasmine equivalent would be (if there is one).
  aliasOfYourController();
  $timeout.flush();
});

如果你想测试$state.go('toSomeState'),那么单元测试用例可以是:

   it('should redirected successfully', function() {
       var stateParams = {
          id: 22,
          name: sample
       }
       functionNameInsideWhichItsBeenCalled(stateParams);
       expect($state.go).toHaveBeenCalledWith('toSomeState', {
           id: stateParams.id,
           name: stateParams.name
       });
   });

现在,很多都只是猜测。没有源代码作为交叉参考,很难建立完全工作的测试套件,但我希望这至少能给你一些使用 $httpBackend、spy 和 stubs 的启示。


谢谢你的回答,我会尽快尝试你的建议。 - ThreeCheeseHigh

0

在单元测试中模拟HTTP响应,你可以使用$httpBackend。

当您希望测试失败时,如果请求未被发出,则使用$httpBackend.expect。 您也可以定义模拟响应。

当您想要定义模拟响应,但不强制执行请求时,请使用$httpBackend.when。

解释的代码...

要测试的控制器代码

self.callme = function() {
    $http.get('/mypath').then(function() {
        console.log("I will be executed when flush is called if the mock API response is 2xx");
    }).catch(function() {
        console.log("I will be executed when flush is called if the mock API response is NOT 2xx");
    });
}

单元测试代码...

$httpBackend.expectGET('/mypath').respond(200, {data:"fred"});
controller.callme();
// The THEN code has not been executed here
$httpBackend.flush();
// The THEN success code is now executed because we responded with 2xx (200)

我在测试中使用了$httpBackend.expectGET$httpBackend.flush();,但是我收到了Error: No pending request to flush !的错误提示。我想知道为了让angular-datatables正常工作,我需要做什么特别的事情。 - Wim Deblauwe
当您调用 flush 时,它表示对于任何未处理的请求现在都要处理。这将触发 $http THEN 语句内的代码。如果您的代码在调用 flush 时还没有发出请求,则会遇到您正在经历的错误。确保在调用 flush 之前,您的代码已经尝试发出 http 请求。 - danday74
我已经更新了我的答案,并添加了一些代码来说明。这些代码是手写的,所以可能会有语法错误,但你应该能够理解。 - danday74
谢谢,但这对我已经很清楚了。但是我想知道针对 angular-datatables 我需要在单元测试代码中调用什么来触发远程调用。 - Wim Deblauwe
好的,我现在明白问题了。你能不能稍微重构一下代码。不要使用fromSource方法来填充表格,有没有什么fromJson方法之类的东西?然后使用$http来填充JSON。这将使单元测试更容易。并不是最理想的答案!很抱歉,我从未使用过ng-datatables。如果没有其他办法,希望这次讨论能帮助某人更好地理解问题。 - danday74

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接