【问题标题】:Using Jasmine spyOn with a method of an object defined in a factory将 Jasmine spyOn 与工厂中定义的对象的方法一起使用
【发布时间】:2017-10-09 12:46:38
【问题描述】:

在我的 Angular JS 应用程序中,我有一个 mainController,它以 userFactory 作为参数。 userFactory 由一个名为 userService 的对象组成,该对象又具有一个 userDetails 对象和一些方法,包括 resetUserDetails。 (往下看)

在 mainController 我有一个调用 userFactory.userService.resetUserDetails 方法的 logOut 函数。我想用 jasmine 测试这个 logOut 函数,但是我遇到了一些错误。我对 Jasmine 很陌生,如果我遗漏了一些明显的东西,我深表歉意。

首先在我的 Jasmine 套件中,我创建了一个 MainControllerSpec 来测试我的 mainController。

在本规范中,我将注入一个名为 userFactory 的工厂。我正在尝试按如下方式监视我的 resetUserDetails 方法,但出现错误:

spyOn(userFactory, 'userService.resetUserDetails');

错误:userService.resetUserDetails() 不存在。

我通过在我的 userFactory(在 userService 对象之外)创建一个名为 test 的函数来尝试这个过程,它运行良好,所以至少我知道规范中的工厂注入设置得很好。
非常感谢任何帮助。谢谢

MainControllerSpec.js

describe("MainController", function () { 
    beforeEach(angular.mock.module('mapModule', 'ngRoute','ngTouch', 'ngAnimate'));
    var scope, userFactory; 

    beforeEach(inject(function($rootScope, $controller, _userFactory_){
        scope = $rootScope.$new();
        userFactory = _userFactory_;
        $controller('mainController', {
            $scope: scope
        }); 
    }));


   describe('The logOut function', function() {
        it('should call the resetUserDetails function of the userFactory.userService object and reset the userDetails object', function() {
            //spyOn takes in a factory and a method of that factory 
            spyOn(userFactory, 'userService.resetUserDetails');
            //spyOn(userFactory, 'test'); tried this and it works.
            scope.logOut();
            expect(userFactory.userService.resetUserDetails).toHaveBeenCalled();  
        });
    });

});

mainController中的logOut函数

   $scope.logOut = function(){
         userFactory.userService.resetUserDetails(); 
         //userFactory.test(); //tried this with spyOn in jasmine
    }

用户工厂

mapApp.factory('userFactory', function(){

    var userService = {
        /*
         * Initialize a userDetails object. 
         */
        userDetails : {   
            "userID" : null,
            "facebookUserID" : "",
            "facebookName" : "",
            "facebookProfilePic" : "",
            "userPrivilegeID" : 1,
            "userToken" : "",
            "isLoggedIn" : false
        },
        resetUserDetails : function(){
            /*
             * This method resets the userDetails object.
             */
            this.userDetails = {
                "userID" : null,
                "facebookUserID" : "",
                "facebookName" : "",
                "facebookProfilePic" : "",
                "userPrivilegeID" : 1,
                "userToken" : "",
               "isLoggedIn" : false
            };
        }
    }; 
    var test = function(){
        /*
        * for testing spyOn in Jasmine
        */
    };
    //return public API so that we can access it in all controllers
    return{
      userService: userService,
      test: test
    };
});

【问题讨论】:

  • 我刚刚成功了。道歉。以下工作正常。 spyOn(userFactory.userService, 'resetUserDetails'); ..
  • 通常你可能想在控制器测试中完全模拟服务,而不是一一监视它的方法。例如。 stackoverflow.com/a/46595428/3731501
  • @estus 好的,谢谢你的建议。我去看看
  • @estus 谢谢,所以在我的情况下,我会做这样的事情来监视工厂(在全局范围内): beforeEach(function() { module('mapModule', { userFactory: { userService: jasmine.createSpy() } }); });然后在套件中: describe('The logOut function', function() {... 我应该省略 spyOn 行吗?我试过了,但没用。谢谢你的帮助。我是 jasmine 的新手跨度>
  • userService 不应该是间谍。它是一个包含resetUserDetails 方法的对象。您的情况与链接答案中的情况完全相同。如果函数已经是间谍,则不需要 spyOn。相反,spy 返回的值应该设置为.and.returnValue(...)(如果需要)。

标签: javascript angularjs jasmine spyon


【解决方案1】:

您需要先模拟您的 userFactory 才能直接注入它。 单元测试的目标是将文件作为黑盒进行测试,而不是直接测试相关方法的逻辑。

对于他们,您将为 userFactory 编写规范文件。

在这种情况下,您可以执行以下操作:

describe("MainController", function() {

  beforeEach(angular.mock.module('mapModule', 'ngRoute', 'ngTouch', 'ngAnimate'));
  var scope, userFactory;

  // here mock the methods of your factory
  beforeEach(module(function($provide) {
    $provide.value('userFactory', {
      myFirstObject: {
        myFirstMethod: function() {}
      }
    });
  }));

  beforeEach(inject(function($rootScope, $controller, _userFactory_) {
    scope = $rootScope.$new();
    userFactory = _userFactory_;
    $controller('mainController', {
      $scope: scope
    });
  }));


  describe('The logOut function', function() {
    it('should call the resetUserDetails function of the userFactory.userService object and reset the userDetails object', function() {
      //here spy on the method and return what you would like to return in this test
      // or if you don't need to manage the return, as it seems you don't, just use callThrough
      spyOn(userFactory.myFirstObject, 'myFirstMethod').and.callThrough();
      scope.logOut();
      expect(userFactory.myFirstObject.myFirstMethod).toHaveBeenCalled();
    });
  });

});

【讨论】:

  • 谢谢。我明天试试这个。尽管我修复了我自己的代码,正如您在上面的 cmets 中看到的那样。我需要这样说: spyOn(userFactory.userService, 'resetUserDetails');而不是这个: spyOn(userFactory, 'userService.resetUserDetails'); ..(因为我的方法在 userFactory 中的一个名为 userService 的对象中)..但我感谢您的建议,并将尝试您的建议。谢谢
  • 哦抱歉没有注意到您的方法在嵌套对象中,更改了答案中的代码
  • 谢谢。我试过了,它运行良好,谢谢。然而,我做了一件额外的事情(感谢 cmets 中的另一个人)。我使用 jasmine.createSpy() 创建了一个间谍,所以 $provide.value('userFactory', { myFirstObject: { myFirstMethod: jasmine.createSpy() } });然后我不需要在套件的测试中使用 spyOn,而是继续使用其余代码。无论如何,你的答案对我的问题是正确的,所以我现在接受它。非常感谢
  • 是的,这种方式也绝对有效,你可以直接在那里做间谍,但如果在未来的测试中你需要间谍只是在一些测试中,在你需要模拟函数之前,你需要正常启动定义然后spyOn
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-12
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 2010-10-15
  • 2012-01-18
相关资源
最近更新 更多