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

Unit testing function, how to implement call to mock object

我正在为控制器编写单元测试,该控制器具有 angular 作用域函数,调用时会调用 parse.com 框架函数,调用解析服务器和 returns 成功参数或错误代码。

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

控制器范围内的函数名称是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();
        }
    });

前言:我不太精通Parse.js(也不是茉莉花)

我建议您去获取 sinonjs - 超级有效并且(一段时间后)易于使用 mocking/stubbing/spying。


首先不要担心 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 我们可以看到方法 returns 是 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;  
  });
}));