【问题标题】:Unit testing function, how to implement call to mock object单元测试功能,如何实现对模拟对象的调用
【发布时间】:2015-07-17 22:07:43
【问题描述】:

我正在为具有角度范围函数的控制器编写单元测试,该函数在调用时调用 parse.com 框架函数,该函数调用解析服务器并返回成功参数或错误代码。

如何为单元测试模拟该对象?

控制器作用域中的函数名称是 Parse.User.logIn,这是迄今为止我为它制作的模拟对象。

mockParse = {
      User: {
        logIn: function(_userName, _password, callbackObj) {
            callbackObj.success({
              attributes:{
                username: "testuser",
                email: "testuser@example.com"
              }
            $rootScope.user = _user;
            $rootScope.isLoggedIn = true;
            $state.go('tab.home');
            };
            callbackObj.error({
              err:{
                code: 101
              }
              if (err.code == 101){
                $scope.error.message = "invalid login credentials";
              } else {
                $scope.error.message = "Unexpected error"
              }
            });
        }
      }
    }

我做对了吗?我是否必须为不同的回调响应、错误和成功创建不同的对象?

当我将它放入测试时,我在哪里注入它?我是否将它放在控制器中,像这样?:

    ctrl = $controller("LoginCtrl", {
        $scope: $scope,
        Parse: mockParse
    });

这是实际的功能:

 Parse.User.logIn(($scope.user.username) , $scope.user.password, {
        success: function(_user) {
            console.log('Login Success');
            console.log('user = ' + _user.attributes.username);
            console.log('email = ' + _user.attributes.email);
            $ionicLoading.hide();
            $rootScope.user = _user;
            $rootScope.isLoggedIn = true;
            $state.go('tab.home');
        },
        error: function(user, err) {
            $ionicLoading.hide();
            // The login failed. Check error to see why.
            if (err.code === 101) {
                $scope.error.message = 'Invalid login credentials';
            } else {
                $scope.error.message = 'An unexpected error has ' +
                    'occurred, please try again.';
            }
            console.log('error message on Parse.User.logIn: ' + $scope.error.message);
            $scope.$apply();
        }
    });

【问题讨论】:

    标签: angularjs unit-testing karma-jasmine


    【解决方案1】:

    前言:我不太熟悉 Parse.js(也不是 jasmine)

    我建议你去抓住sinonjs - 超级有效并且(一段时间后)易于用于模拟/存根/间谍活动。


    首先不要担心Parse 对象的内部实现,在单元测试设置中,我们只对inputoutput 的依赖项感兴趣。应该没有理由在精细的细节中剔除服务对象。


    beforeEach

    var $scope, Parse, createController;
    
    beforeEach(function () {
      Parse = {};
    
      module('your_module', function ($provide) {
        // Provide "Parse" as a value in your module.
        $provide.value('Parse', Parse);
      });
    
      inject(function ($controller, $injector) {
        $scope = $injector.get('$rootScope').$new();
        Parse  = $injector.get('Parse'); // equal to: {} (at this point)
    
        createController = function () {
          return $controller('LoginCtrl', {
            $scope: $scope,
            Parse:  Parse
          });
        };
      });
    
      Parse.user = {};
      Parse.user.login = sinon.stub();
    });
    

    Parse 现在完全被排除了。它将是一个空对象,然后提供给模块,在我们的规范中公开,注入我们的控制器,然后用与实际实现匹配的存根方法进行装饰(当然只有名称)。


    期望

    您应该在控制器规范中测试的不是Parse 服务的内部行为,而是您的控制器使用正确的参数调用Parse 的方法。

    之后,您可以继续测试您的控制器,以及其关联的$scopeParse.user.login()响应的反应。


    注意:我正在使用 mocha/chai 语法编写这些规范 - 采用你认为合适的 jasmine 风格(老实说,不确定它如何与 sinon.js 搭配使用)

    it('calls the Parse method', function () {
      createController();
    
      $scope.login('username', 'pw', angular.noop);
    
      expect(Parse.user.login).to.have
        .been.calledOnce.and.calledWith('username', 'pw', angular.noop);
    });
    
    context('Parse response', function () {
      it('failed', function () {
        Parse.user.login.returns({ status: 'super_error' });
    
        createController();
    
        $scope.login('a', 'b', angular.noop);
    
        expect(/* expect that something on $scope happened? */);
      });
    
      it('was succesful', function () {
        Parse.user.login.returns({ status: 'great_success', data: {} });
    
        createController();
    
        $scope.login('a', 'b', angular.noop);
    
        expect(/* expect that something on $scope happened? */);
      });
    });
    

    我就是这样写的。但是 - 查看Parse.user.logIn docs,我们可以看到该方法返回promise

    因此,您需要采取以下步骤来有效地消除正确的行为:

    var $q; 
    
    // combine this beforeEach block with the first one we wrote. 
    beforeEach(function () {
      inject(function ($injector) {
        $q = $injector.get('$q');
      });
    });
    
    context('parse.user.login', inject(function ($timeout) {
      var success, error, opts;
    
      beforeEach(function () {
        success = sinon.stub();
        error   = sinon.stub();
    
        opts = {
          success: success,
          error:   error
        };
      });
    
      it('failed', function () {
        Parse.user.logIn.returns($q.reject({ status: 'bad_error' });
        createController();
    
        $scope.login('a', 'b', opts);
        $timeout.flush(); // Either this, or $rootScope.$digest or .notify(done) to flush the promise.
    
        expect(error).to.have.been.calledOnce;
      });
    
      it('was successful', function () {
        Parse.user.logIn.returns($q.when({ status: 'yay!', data: {} });
        createController();
    
        $scope.login('a', 'b', opts);
        $timeout.flush(); // Either this, or $rootScope.$digest or .notify(done) to flush the promise.
    
        expect(success).to.have.been.calledOnce;  
      });
    }));
    

    【讨论】:

    • 我实际上只是注意到我的 glob 模式没有找到 parse.js 文件,这就是我收到 Parse undefined 错误的原因。现在我添加了它,并且必须将我的初始化密钥放在我所做的 beforeEach 中,现在我收到一个错误,显示为 Error: Expected a spy, but got Function. 现在这是怎么回事?这仍然需要 sinon 存根吗?
    • 好吧,从您向我们展示的内容来看,您的 mockParse 对象并没有设置任何间谍。另外,正如我所说,它太详细了——你不需要/想要所有这些。我会一直推荐 sinon,因为它是一个专门用于间谍/存根/模拟的库,而 Jasmine 不是。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多