Im new to jasmine/karma, trying to write a test for my angular application and i have a problem that i can't solve, hopefully someone here can help me.
My problem is that my login() function updates a value inside my controller but jasmine fails to see the updated value when it's inside .then() and the test fails, but when i update the value outside of the .then() it passes successfully.
here is my controller:
var Authctrl = this;
Authctrl.myVariable = "oldValue";
Authctrl.login = function () {
AuthService.login(Authctrl.credentials).then(function(authData){
Authctrl.credentials = { email: '',password: ''};
/*case 1 */ Authctrl.myVariable = "newValue"; //test gives error
},function(error){
console.log(error);
Authctrl.errors.login = 'Wrong username or password. Please try again';
});
/*case 2 */ Authctrl.myVariable = "newValue"; //test passes successfully
};
and my test code:
it('should be newValue',function(){
Authctrl.credentials = {
email: 'myEmail#yahoo.com',
password: '12345'
};
Authctrl.login();
expect(Authctrl.myVariable).toBe('newValue');
});
and my service:
authService.login = function (credentials) {
return $q(function(resolve, reject){
ref.authWithPassword(credentials , function(error, authData) {
if (error === null) {
// user authenticated with Firebase
console.log('SERVICE IS RUNNING, success'); //this does not log when testing with karma
resolve(authData);
} else {
console.log('SERVICE IS RUNNING, error'); //this does not log when testing with karma
reject(error);
}
},{
remember: "default"
});//ref.authWithPassword end
console.log('SERVICE IS RUNNING'); //this logs when testing with karma
});//$q end
};//authService.login end
so I finally figured this out and decided to post the answer here in case others run into the same problem. as #MatthewGreen mentioned, i had to create a mock service. and use the $provide to define mock AuthService methods and their return values. i followed many tutorials online and i kept getting errors, then i learnt that few things have changed in new jasmine and one of them is the spyOn command. This tutorial helped me a lot.
'use strict';
describe('Controller: AuthCtrl', function () {
var $rootScope,$scope,$controller,AuthService,AuthCtrl;
//fake firebase user data
var mockAuthData = {
provider: 'password',
password:{
email: 'myEmail#yahoo.com',
isTemporaryPassword: false
},
auth:{
provider:'password',
uid:'simplelogin:1'
},
uid:'simplelogin:1'
};
beforeEach(function() {
module('myApp');
// Provide will help us create fake implementations for our dependencies
module(function($provide) {
// Fake AuthService Implementation returning a promise
$provide.value('AuthService', {
login:function(){
return{
then:function(callback){return callback(mockAuthData);}
};
}
});
return null;
});
});
// load the controller's module
beforeEach(inject(function(_$rootScope_, _$controller_, _$q_, _AuthService_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$controller = _$controller_;
AuthService = _AuthService_;
AuthCtrl = $controller('AuthCtrl',
{'$rootScope' : $rootScope, '$scope': $scope, 'AuthService': AuthService});
$rootScope.$apply();
}));
it("myVariable should be newValue", function() {
spyOn(AuthService, 'login').and.callThrough();
AuthCtrl.login();
expect(AuthService.login).toHaveBeenCalled();
expect(AuthCtrl.myVariable).toBe('newValue');
});
it("should retrieve the email address", function() {
spyOn(AuthService, 'login').and.callThrough();
AuthCtrl.login();
expect(AuthService.login).toHaveBeenCalled();
expect(AuthCtrl.userEmail).toBe('myEmail#yahoo.com');
});;
});
Related
I am just starting getting my head around unit testing and angular 1.2. I am using the $httpBackend service for testing a $httpcall in a service. This service is called inside my controller:
var app = angular.module('myApp', []);
app.controller('MainCtrl', function ( someService) {
var vm = this;
vm.hasError = false;
//debugger;
someService.someAsyncCall()
.then(function (data) {
vm.hasError = false;
})
.catch(function (data) {
vm.hasError = true;
});
});
app.factory('someService', function ($http) {
return {
someAsyncCall: function () {
return $http.get("/data")
.success(function (data) {
//return true
})
.error(function () {
console.log('error');
//return false
});
}
};
});
I am trying to write a test for when the endpoint is wrong:
beforeEach(function () {
someServiceMock = jasmine.createSpyObj('someService', ['someAsyncCall']);
module('myApp');
inject(function ($rootScope, $controller, $q, _$timeout_,$httpBackend) {
$scope = $rootScope.$new();
someServiceMock.someAsyncCall.andReturn($q.when('weee'));
$timeout = _$timeout_;
controllerService = $controller;
httpMock = $httpBackend;
});
});
it("should set hasError=true with error request", function () {
httpMock.expectGET("/datajfds").respond(false);
ctrl = controllerService('MainCtrl', {someService: someServiceMock});
expect(ctrl.hasError).toBe(true);
});
This is the error:
MainCtrl testing should set hasError=true with error request.
Expected false to be true.
Error: Expected false to be true.
at new jasmine.ExpectationResult (http://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js:114:32)
at .toBe (http://cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js:1235:29)
at .<anonymous> (http://run.plnkr.co/Q77IyPb6agUqgR2U/specs.js:37:31)
Basically I would like to get coverage/write a test for when the someService.someAsyncCall() will catch an error. How can I write this test so that ctrl.hasError =true?
plunkr:http://plnkr.co/edit/aX29h4k64ZUm6hDedZl6?p=preview
There were lots of problems with what you were doing and I don't understand what you are trying to do with the third test. The api that you hit is configured in the service.
$http.get("/data")
See this plunker which might be that the tests that you were trying to do. Some problems:
You were injecting a lot of things that weren't needed
Your service call wasn't returning anything.
You weren't getting the value from the service call
I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});
I'm attempted to unit test a service. I've injected the service however the method call getAllProducts() doesn't appear to run however the test still passes!
Plnkr
service.js
angular.module('vsApp')
.factory('productsDataService', function($http) {
var service = {
getAllProducts: getAllProducts
};
// get all products
function getAllProducts() {
return $http.get('/apiv1/getAllProducts/').then(function(data) {
return (data);
});
}
return service;
});
spec.js
// jasmine
describe('products data service', function () {
var $httpBackend, productsDataService;
beforeEach(module('vsApp'));
beforeEach(inject(function(_$httpBackend_, _productsDataService_) {
$httpBackend = _$httpBackend_;
productsDataService = _productsDataService_;
}));
it('should get all products', inject(function() {
console.info("get all");
// mock response for the http call in the service
$httpBackend.when('GET', '/apiv1/getAllProducts/')
.respond({name: 'item', price: '932'});
//this doesn't seem to run??
productsDataService.getAllProducts().then(function(response) {
expect(response.data.length).toBeGreaterThan(1);
});
}));
});
Ok, you have to make it sync. (all pending request will get resolved) using $http.flush();
Working demo as expected
productsDataService.getAllProducts().then(function(response) {
console.log(response);
expect(response.data.length).toBeGreaterThan(999);
});
$httpBackend.flush(); // <=============== here.
I am calling an API service which returns a promise from a factory.
Here is a part of my factory.
factories.factory('OnBoardingFactory', ['$http',
function ($http) {
var dataFactory = {};
dataFactory.get = function (url) {
return $http.get('http://localhost/api/onboarding/' + url)
};
return dataFactory
}
]);
And here is where its called from the controller:
OnBoardingFactory.get('login?username=test&password=password')
.then(function(response){
$scope.response = response.status;
})
This returns data in the controller absolutely fine. However I have difficulties when I come to test it. Here is my test script:
var scope, FakeOnBoardingFactory, controller, q, deferred;
beforeEach(module('app.module'));
beforeEach(function () {
FakeOnBoardingFactory = {
get: function () {
deferred = q.defer();
// Place the fake return object here
deferred.resolve({ response: {status: 200}});
return deferred.promise;
}
};
spyOn(FakeOnBoardingFactory, 'get').and.callThrough();
});
beforeEach(inject(function ($q, $rootScope, $controller, $injector ) {
scope = $rootScope.$new();
q = $q;
controller = $controller(OnBoardingCtrl, {
$scope: scope,
OnBoardingFactory: FakeOnBoardingFactory
})
}));
it('Should call the form and return 200', function () {
// Execute form
scope.loginCredentials({$valid: true});
scope.$apply();
// Ensure script is called (which passes fine)
expect(FakeOnBoardingFactory.get).toHaveBeenCalled();
scope.$apply();
// BREAKS HERE
expect(scope.status).toBe(200);
})
When expect(FakeOnBoardingFactory.get).toHaveBeenCalled(); is called, this passes fine. However then I run expect(scope.status).toBe(200), it breaks "Expected undefined to be 200".
This would indicate that my FakeOnBoardingFactory isn't returning any data. But I can't seem to find the issue.
It must be the change to support multiple body assertions that has caused this bug.
The workaround for now is to either don't use expect and do your assertion in the end function callback.
So instead of .expect(200) it would be.
end(function(err,res) { res.status.should.equal(200) },
or if you do use expect.. you need to make sure you specify a body as well as just a status..
it('should assert status only 1', function(done){
var app = express();
app.get('/user', function(req, res){
res.send(201, { name: 'tobi' }); }); request(app) .get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '20')
.expect(201)
.end(function(err, res){
if (err) throw err;
});
})
I have seen some questions regarding this but all of them was specific to each case and I couldn't find a solution for my case in those posts.
I have a current controller:
function Login(authService, $scope) {
var vm = this;
vm.submit = submit;
vm.form = {};
function submit() {
if ($scope.loginForm.$invalid) {
vm.invalid = true;
return;
} else {
var data = {
usr: vm.form.email,
pwd: vm.form.password,
vendorId: 99
};
authService.login(data).then(success, error);
}
}
function success(res) {
if (res.data) {
//Do stuff
}
}
function error(error) {
console.log("Error ", error);
}
}
And the following unit test:
describe('Login', function() {
beforeEach(module('app'));
var loginCtrl, scope, $httpBackend, authService;
var loginResponse = [{
"data": {
"avatar": "avatar",
"gender": "M",
"hid": "hid,
"id": "id",
"role": "Adult",
"token": "token"
}
}];
var loginRequest = { "usr": "test#teste.com", "pwd": "123teste!", "vendorId": 99 };
beforeEach(inject(function($rootScope, _$httpBackend_, $controller, _authService_) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
loginCtrl = $controller('Login', {
$scope: scope
});
authService = _authService_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe("submit", function() {
it("should send login data to the server", function() {
// expect(loginCtrl.login).toBe(false);
//Tells the $httpBackend service to expect a POST call to be made to a service and that it will return
//loginResponse object that was defined before
$httpBackend.expectPOST('api/current/api/login').respond(loginResponse);
//Execution of the service
var deferred = authService.login(loginRequest);
var users;
deferred.then(function(response){
users = response.data;
});
// expect(loginCtrl.login).toBe(true);
//Preserve the asynchronous nature of the call while at the same time be able to test the response of the call
$httpBackend.flush();
// dump(users);
expect(users).toEqual(loginResponse);
// expect(loginCtrl.login).toBe(true);
});
});
});
And I am getting the error:
Error: Unexpected request: GET signup/signup.html
No more request expected
I have found why this error occurs (I think). I'm using ui-router and it seems that it is always trying to do a GET request for the router root:
$urlRouterProvider.otherwise('/signup/');
$stateProvider
/* NOT AUTHENTICATED STATES */
.state('signup', {
url: '/signup/',
templateUrl: 'signup/signup.html',
controller: 'Signup as signupCtrl',
data: {
authorizedRoles: [AUTH_EVENTS.notAuthenticated]
}
})
Now I have no idea why, or how to fix it... Can someone understand what I'm doing wrong?
Thanks in advance!
Edit: authService
function authService($http, Session) {
var service = {
login : login
};
return service;
function login(credentials) {
console.log('authservice:', credentials);
return $http.post('api/current/api/login', credentials).then(function(res){
if (res.data.data){
var user = res.data.data;
Session.create(user.id, user.hid, user.token, credentials.usr, user.role, user.gender);
}
return res.data;
});
}
}
The template is requested, but you didn't inform the $http mock that it would be.
Register your template with $httpBackend
$httpBackend.expect('GET', 'signup/signup.html');
https://docs.angularjs.org/api/ngMock/service/$httpBackend#expect
Updated your function and try this code should work. Another option is to add ur template in template cache using gulp if you are using gulp this template get call problem is very specific with ui router and i have seen bug posted against it in github.
$httpBackend.expectGET('signup/signup.html').respond(200);
$httpBackend.flush();
$httpBackend.expectPOST('api/current/api/login').respond(loginResponse);
//Execution of the service
var deferred = authService.login(loginRequest);
var users;
deferred.then(function(response){
users = response.data;
});
// expect(loginCtrl.login).toBe(true);
//Preserve the asynchronous nature of the call while at the same time be able to test the response of the call
$httpBackend.flush();
// dump(users);
expect(users).toEqual(loginResponse);
// expect(loginCtrl.login).toBe(true);