I'm writing a unit tests for a controller which has an angular scope function that when called, calls a parse.com framework function that makes a call to the parse server and returns the success parameters or the error code.
How do I go about making a mock of that object for the unit test?
The name of the function in the controller's scope is Parse.User.logIn, and here is the mock object I've made for it so far.
mockParse = {
User: {
logIn: function(_userName, _password, callbackObj) {
callbackObj.success({
attributes:{
username: "testuser",
email: "[email protected]"
}
$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"
}
});
}
}
}
Did I do that right? Do I have to make different objects for different callback responses, the error and the success?
When I put it in the test, where do I inject it? Do I place it in the controller, like this?:
ctrl = $controller("LoginCtrl", {
$scope: $scope,
Parse: mockParse
});
Here's the actual function:
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();
}
});
I would recommend you go and grab sinonjs - super effective and (after some time) easy to use for mocking/stubbing/spying.
Start off by not worrying about the internal implementation of the Parse
object, we're only interested in input
and output
of our dependencies when in a unit testing setting. There should be no reason to stub out a service object in that fine grade detail.
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 is now entirely stubbed out. It will be an empty object that is then provided to the module, exposed in our spec, injected into our controller and then decorated with stubbed out methods matching those of the actual implementation (only by name, of course).
What you should be testing in your controller spec, is not the internal behaviour of the Parse
service, but rather that your controller is calling the methods of Parse
with the correct parameters.
After that, you can go on to test what your controller, and its associated $scope does in reaction to the Parse.user.login()
response.
Note: I am writing these specs with the mocha/chai syntax - adopt the jasmine style as you see fit (not sure how it plays with sinon.js to be quite honest)
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? */);
});
});
That is how I would write it. But - looking at the Parse.user.logIn docs we can see that the method returns a promise
.
As such, you will need to take the following steps to effectively stub out the correct behaviour:
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;
});
}));
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments