单元测试功能,如何实现对mock对象的调用

时间:2015-07-17 22:07:44

标签: angularjs unit-testing karma-jasmine

我正在为一个控制器编写一个单元测试,该控制器具有一个角度范围函数,当调用它时,调用一个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();
        }
    });

1 个答案:

答案 0 :(得分:0)

前言:我不熟悉Parse.js(也不是茉莉花)

我建议你去抓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现在完全被删除了。它将是一个空对象,然后提供给模块,在我们的规范中公开,注入到我们的控制器中,然后使用与实际实现匹配的存根方法进行修饰(当然,仅通过名称)。

期望的(S)

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

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

它' S

注意:我正在使用mocha / chai语法编写这些规范 - 采用您认为合适的茉莉风格(不确定它与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;  
  });
}));