【问题标题】:Should I mock ALL external Services in angular?我应该以角度模拟所有外部服务吗?
【发布时间】:2015-08-24 20:15:36
【问题描述】:

我目前正在开始进行角度单元测试。由于我想测试的第一个控制器看起来像这样,我很困惑。

angular.module('sgmPaperApp')
  .controller('AccountCtrl', function ($mdToast, user, $firebaseArray, Ref) {
var vm = this;
vm.data = user;
vm.save = saveUser;
vm.comments = $firebaseArray(Ref.child('comments').orderByChild('person').equalTo(user.$id));


function saveUser() {
  vm.data.$save().then(function () {
    $mdToast.showSimple('Data saved');
  });
}
});

我真的应该模拟我使用的所有外部服务吗?毕竟控制器并不多,外部服务和模拟 firebaseArray 可能很困难。

感谢您的建议并帮助我开始测试

【问题讨论】:

  • 是的,否则你不是单元测试。您对您的代码有实际问题还是只是在征求意见?
  • 我追求的是意见;)嘲笑这一切感觉很奇怪。但是我如何模拟 Ref 服务,这给了我一个 firebase 参考?

标签: angularjs unit-testing angularfire


【解决方案1】:

您无需担心外部依赖项的作用,只需模拟它们的 API。

这些是我能看到的唯一模拟。我假设你正在使用 Jasmine

var Ref, $firebaseArray, $mdToast, user, vm;

beforeEach(function() {
    Ref = jasmine.createSpyObj('Ref', ['child', 'orderByChild', 'equalTo']);
    Ref.child.and.returnValue(Ref);
    Ref.orderByChild.and.returnValue(Ref);
    Ref.equalTo.and.returnValue(Ref);

    $firebaseArray = jasmine.createSpy('$firebaseArray').and.returnValue('comments');
    $mdToast = jasmine.createSpyObj('$mdToast', ['showSimple']);
    user = jasmine.createSpyObj('user', ['$save']);
    user.$id = 'id';

    module('sgmPaperApp'); // you should consider separate modules per "thing"

    inject(function($controller) {
        vm = $controller('AccountCtrl', {
            $mdToast: $mdToast,
            user: user,
            $firebaseArray: $firebaseArray,
            Ref: Ref
        });
    });
});

然后您可以轻松创建测试

it('assigns a bunch of stuff on creation', function() {
    expect(vm.data).toBe(user);
    expect(vm.comments).toEqual('comments'); // that's what the mock returns

    expect(Ref.child).toHaveBeenCalledWith('comments');
    expect(Ref.orderByChild).toHaveBeenCalledWith('person');
    expect(Ref.equalTo).toHaveBeenCalledWith(user.$id);
    expect($firebaseArray).toHaveBeenCalledWith(Ref);
});

您甚至可以测试基于 Promise 的方法,例如 saveUser

it('saves the user and makes some toast', inject(function($q, $rootScope) {
    user.$save.and.returnValue($q.when()); // an empty, resolved promise
    vm.saveUser();
    expect(user.$save).toHaveBeenCalled();
    expect($mdToast.showSimple).not.toHaveBeenCalled(); // because the promise hasn't resolved yet

    $rootScope.$apply(); // resolves promises

    expect($mdToast.showSimple).toHaveBeenCalledWith('Data saved');
}));

【讨论】:

  • 很多thnaks,是的,我正在使用茉莉花,我会从我不明白的答案中阅读一些内容。因为yoeman生成器,我不使用模块,对测试有很大影响吗?
  • @Isigiel 在某种程度上,是的。 module 函数引导提供的模块,如果你将所有内容集中在一起,你并没有完全构建/测试独立的组件。
  • @Isigiel 看看angular-seed project 如何构建各种模块、服务、指令和控制器。
  • 另外,当使用该代码时,似乎原始的 firebase 服务被使用并崩溃了
  • @Isigiel 我在 $controller 的本地 arg 中有一个错字(我有 $firebadseArray)。现在已经修复了
【解决方案2】:

因此,要回答这个问题,我们需要考虑我们实际想要做什么。如果我们尝试单元测试,那么是的,我们需要模拟所有依赖项。

不过,模拟你的依赖并不难。你只需要模拟你正在使用的东西。

例如,$firebaseArray 一开始是一个接收参数的函数,我们知道很多:

var mockFirebaseArray = function(ref) {
};

接下来,在完成之前,我们需要模拟Ref

var mockRef = {
  child: function(path) {
    this.orderByChild = function(path) {
      this.equalTo = function(val) {
      };
      return this;
    };
    return this;
  }
};

有了这些东西,我们可以决定测试将如何“通过”。我们可以只使用间谍。或者,我们可以设置局部变量,以便稍后在通过时断言。

间谍是我的首选方法,因为您甚至可以验证它们是使用特定值调用的:

expect(mockFirebaseArray).toHaveBeenCalled();
expect(mockRef.child).toHaveBeenCalledWith('comments');

现在,如果您想编写一个不同的集成测试。在那种情况下,我仍然会使用间谍,但您实际上会执行这些依赖项。一般来说,不需要测试您的依赖项,因为它们也应该单独测试。此外,如果其他人的 API 来自可信赖的来源,则无需对其进行测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    • 1970-01-01
    • 2021-10-12
    • 1970-01-01
    • 2014-05-27
    • 2021-06-19
    • 2016-03-01
    相关资源
    最近更新 更多