在使用jasmine进行单元测试时,如何在AngularJS中模拟服务?

65

假设我有一个名为shop的服务,它依赖于两个有状态的服务schedulewarehouse。如何在单元测试中将不同版本的schedulewarehouse注入到shop中?

这是我的服务:

angular.module('myModule').service('shop', function(schedule, warehouse) {
    return {
        canSellSweets : function(numRequiredSweets){
             return schedule.isShopOpen()
                 && (warehouse.numAvailableSweets() > numRequiredSweets);
        }
    }
});

以下是我的模拟测试:

var mockSchedule = {
    isShopOpen : function() {return true}
}
var mockWarehouse = {
    numAvailableSweets: function(){return 10};
}

这是我的测试:

expect(shop.canSellSweets(5)).toBe(true);
expect(shop.canSellSweets(20)).toBe(false);
8个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
53
beforeEach(function () {
  module(function ($provide) {
    $provide.value('schedule', mockSchedule);
  });
});

Module是由angular-mocks模块提供的一个函数。如果传入一个字符串参数,则会加载与该名称对应的模块,并且所有的提供者、控制器、服务等都可以在测试中使用。通常它们使用inject函数来加载。如果传入的是回调函数,它将使用Angular的$injector服务进行调用。这个服务然后查看传递给回调函数的参数,并试图推断出应该传递什么依赖项到回调函数中。


1
您是否真的需要额外的包装函数?即,是否可能直接将“模块”函数传递给“beforeEach”? - Jim Aho
module 调用周围的函数实际上是不必要的。 - Jelle den Burger

37

在改进Atilla的回答并直接回应KevSheedy的评论时,针对module('myApplicationModule')的上下文,您需要执行以下操作:

beforeEach(module('myApplicationModule', function ($provide) {
  $provide.value('schedule', mockSchedule);
}));

2
那么这个语法基本上是模拟“schedule”(或任何服务、控制器、工厂)在“myApplicationModule”中的正确方法吗? - Jim Aho

7

使用CoffeeScript时,我遇到了一些问题,因此在结尾处使用null

beforeEach ->
  module ($provide) ->
    $provide.value 'someService',
      mockyStuff:
        value : 'AWESOME'
    null

7
为了避免coffeescript的隐式返回,你可以在函数末尾加上return语句,这样函数就不会返回任何东西而不是返回null。参见https://dev59.com/umUp5IYBdhLWcg3wJlEH - endorama
1
如果你写成“return”,它会返回“undefined”--但两种方式都可以正常工作,而且这种方式比较短;)--无论哪种方式,你应该在该行添加注释,因为不小心删除它的人将遭受你第一次经历的痛苦。 - Nick Perkins
这解决了我(非常)令人沮丧的问题。基本上,一旦我添加了$provide来进行模拟,我的angular.mock.inject就不再运行/工作了。返回null让它们能够很好地协同工作。 - Shiboe

6

2
由于您正在使用jasmine,因此有一种替代方法可以使用jasmine的间谍(https://jasmine.github.io/2.0/introduction.html#section-Spies)来模拟调用。 使用这些间谍,您可以针对您的函数调用进行定位,并在需要时允许原始对象的调用。这避免了在测试文件顶部堆积$provide和模拟实现的情况。 在测试的beforeEach中,我会写上类似以下的代码:
var mySchedule, myWarehouse;

beforeEach(inject(function(schedule, warehouse) {

  mySchedule = schedule;
  myWarehouse = warehouse;

  spyOn(mySchedule, 'isShopOpen').and.callFake(function() {
    return true;
  });

  spyOn(myWarehouse, 'numAvailableSweets').and.callFake(function() {
    return 10;
  });

}));
这应该与 $provide 机制类似工作,需要注意的是您必须提供要进行监视的注入变量的本地实例。

1

将模拟数据放在模块中更加简单:

    beforeEach(function () {
    module('myApp');
    module({
      schedule: mockSchedule,
      warehouse: mockWarehouse
     }
    });
  });
你可以使用注入来获取这些模拟对象的引用,以进行预测试操作:
var mockSchedule;
var mockWarehouse;

beforeEach(inject(function (_schedule_, _warehouse_) {
     mockSchedule = _schedule_;
     mockWarehouse = _warehouse_;
}));

1

我希望我的回答不是无用的,但是您可以通过$provide.service来模拟服务。

beforeEach(() => {
    angular.mock.module(
      'yourModule',
      ($provide) => {
        $provide.service('yourService', function() {
          return something;
        });
      }
    );
  });

1
我最近发布了ngImprovedTesting模块,这应该会使在AngularJS中进行模拟测试变得更加容易。 在您的示例中,您只需替换Jasmine测试中的...
beforeEach(module('myModule'));

... with ...

beforeEach(ModuleBuilder.forModule('myModule').serviceWithMocks('shop').build());

欲了解有关ngImprovedTesting的更多信息,请查看其介绍性博客文章: http://blog.jdriven.com/2014/07/ng-improved-testing-mock-testing-for-angularjs-made-easy/


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