I am learning BDD with Jasmine and Karma.
I'm following John Papa's style guide and I have some difficulties testing my code.
I want to test this kind of controller function (from John Papa's guide):
function getAvengers() {
return dataservice.getAvengers()
.then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
With this factory:
function dataservice($http) {
return {
getAvengers: getAvengers
};
function getAvengers() {
return $http.get('/api/maa')
.then(getAvengersComplete)
.catch(getAvengersFailed);
function getAvengersComplete(response) {
return response.data.results;
}
function getAvengersFailed(error) {
// Fail
}
}
}
I've made these test:
describe("avengers controller", function() {
var controller, avengers;
beforeEach(function() {
module('app');
});
beforeEach(inject(function(_$rootScope_, _$controller_, _$q_, _dataservice_) {
dataserviceMock = _dataservice_;
controller = _$controller_;
vm = controller("AvengerController", { dataservice: dataserviceMock });
$q = _$q_;
rootScope = _$rootScope_;
avengers = [{"id": 1}, {"id": 2}];
}));
it('should be created successfully', function () {
expect(vm).toBeDefined();
});
it('should have called `dataservice.getAvengers`', function() {
spyOn(dataserviceMock, 'getAvengers').and.callFake(function(){
var deferred = $q.defer();
return deferred.promise;
});
vm.getAvengers();
expect(dataserviceMock.getAvengers).toHaveBeenCalledTimes(1);
});
// What I'm trying to test
// Test 1
it('should have Avengers', function() {
spyOn(dataserviceMock, 'getAvengers').and.callFake(function(){
var deferred = $q.defer();
deferred.resolve([{"id": 1}, {"id": 2}]);
return deferred.promise;
);
vm.getAvengers();
expect(vm.avengers.length).toEqual(avengers.length);
});
// Test 2
it('should have Avengers', function() {
spyOn(dataserviceMock, 'getAvengers').and.returnValue(avengers);
vm.getAvengers();
expect(vm.avengers.length).toEqual(avengers.length);
});
});
I tried two different ways but it didn't work and I can't figure out how to test that avengerController.getAvengers() defines vm.avengers equal to the returned data avengers from the factory call.
I found a solution:
it("should have avengers", function() {
deferred = $q.defer();
spyOn(dataserviceMock, 'getAvengers').and.returnValue(deferred.promise);
deferred.resolve(customers);
vm.getAvengers();
expect(dataserviceMock.getAvengers).toHaveBeenCalled();
scope.$digest();
expect(vm.avengers).toBeDefined();
expect(vm.avengers).toEqual(avengers);
});
I had to return a promise and use the scope.$digest() to simulate the scope life cycle as mentionned here.
Related
I wrote some unit tests for an angular factory that mock the response with a fake promise using $q.resolve({}). I'm now writing the controller test and want to call the factory from the controller and assign the resolved mock factory response to the controller scope (vm), and verify the data. Is this the proper way to test a factory from within a controller?
factory test
describe('getVICs()', function() {
var vicFactory, $q, $httpBackend, result;
beforeEach(module('vicModule'));
beforeEach(inject(function(_vicFactory_, _$q_, _$httpBackend_) {
vicFactory = _vicFactory_;
$q = _$q_;
$httpBackend = _$httpBackend_;
}));
beforeEach(function() {
result = {};
spyOn(vicFactory, 'getVICs').and.callThrough(function() {
var deferred = $q.defer();
return deferred.promise;
});
});
it('should be defined', function() {
expect(vicFactory.getVICs()).toBeDefined();
});
it('should return list of vics', function() {
var vicList = {
"results": [
{
"version": 5
},
{
"version": 1
}
]
};
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
$httpBackend.expectGET('/path/to/my/api' + qs).respond(200, vicList);
$httpBackend.whenGET('http://localhost:3000/path/to/my/api' + qs).respond(200, $q.when(vicList));
vicFactory.getVICs(qs)
.then(function(response) {
result = response;
});
// flush pending HTTP requests
$httpBackend.flush();
expect(result.data.results).toBeDefined();
});
});
factory
function getVICs(query) {
return $http.get('/path/to/my/api' + query)
.then(function(response) {
return response;
});
}
controller test
describe('vicController', function() {
var $controller, vicController, vicFactory, $q, $scope, $httpBackend, deferred;
var vicList = {
"results": [
{
"version": 5
},
{
"version": 1
}
]
};
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
beforeEach(module('vicModule'));
beforeEach(inject(function(_$controller_, _vicFactory_, _$q_, _$rootScope_, _$httpBackend_, _$httpParamSerializer_) {
$controller = _$controller_;
vicFactory = {
getVICs: function () {
deferred = $q.defer();
return deferred.promise;
}
};
$q = _$q_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
spyOn(vicFactory, 'getVICs').and.callThrough();
vicController = $controller('vicController', {vicFactory: vicFactory});
deferred.resolve(vicList);
}));
it('should be defined', function() {
expect(vicController).toBeDefined();
});
it('should call getVICs() on load', function() {
$httpBackend.expectGET(''/path/to/my/api?' + qs).respond(200, vicList);
$scope.$digest();
expect(vicFactory.getVICs).toHaveBeenCalledWith(qs);
expect($scope.vicList).toEqual(vicList);
});
});
controller
angular.module('vicModule')
.controller('vicController', vicController);
vicController.$inject = [
'vicFactory'
];
function vicController(vicFactory) {
var vm = this;
var qs = '?sort=version&dir=DESC&page=1&limit=10&status=ACTIVE';
vicFactory.getVICs(qs)
.then(function(response) {
console.log('vic controller success');
console.log(response);
vm.vicList = response;
}, function(response) {
console.log('vic controller error');
console.log(response);
});
}
Fixed
Got this working by injecting $q and $scope, setting vicFactory in beforeEach, updating spy, and then resolving the deferred with the expected data set after controller initialization. The controller test spec has been updated, hope this help someone.
I would like to test my then and catch function from my $scope.customerinfo. The problem is i dont know how exactly.
var app = angular.module('shop', ['ngRoute','ngResource'])
.factory('Customerservice', function ($resource) {
return $resource('http://localhost:8080/Shop/:customer',{customer: "#customer"});
})
.controller('customerController', function ($scope,Customerservice) {
$scope.customerinfo = CustomerService.get({customer: "Mark"});
$scope.customerinfo.$promise.then(function(info) {
return info;
}).catch(function(errorResponse) {
throw errorResponse;
});
});
Im not done yet but this is my jasmine code
describe('Testing the customerinfo', function () {
var $scope;
var $q;
var deferred;
beforeEach(module('shop'));
beforeEach(inject(function($controller, _$rootScope_, _$q_) {
$q = _$q_;
$scope = _$rootScope_.$new();
deferred = _$q_.defer();
$controller('userController', {
$scope: $scope
});
}));
it('should reject promise', function () {
// I want to check if the catch option is working
});
});
So how exactly can i do this, or do i need to refactor the code?
The jasmine 'it' method takes a done parameter that you can call for async testing
it('Should reject', function(done) {
someAsyncFunction().catch(function(result) {
expect(result.status).toBe(401);
done();
});
});
i begin to use Jasmine in unit testing angularjs and see a lot example but not work i have usersservice and i need to make unit test for it
Please i need work demo
(function () {
'use strict';
angular
.module('app')
.factory('UserService', UserService);
UserService.$inject = ['$http'];
function UserService($http) {
var service = {};
service.GetAll = GetAll;
return service;
function GetAll(page) {
return $http.get('https://api.github.com/users').then(handleSuccess, handleError('Error getting all users'));
}
// private functions
function handleSuccess(res) {
return res.data;
}
function handleError(error) {
return function () {
return { success: false, message: error };
};
}
}})();
Below is a test demo for your service:
describe("UserService", function() {
var service;
var $rootScope;
var $httpBackend;
var dataMock = ["John", "Albert", "Mary"];
beforeEach(module('app'));
beforeEach(inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$httpBackend = $injector.get('$httpBackend');
service = $injector.get('UserService');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should fetch 3 users', function() {
$httpBackend.when('GET', 'https://api.github.com/users').respond(dataMock);
service.GetAll().then(function(users) {
expect(users.length).toBe(3);
expect(users).toEqual(dataMock);
});
$httpBackend.flush();
});
it('should return error', function() {
$httpBackend.when('GET', 'https://api.github.com/users').respond(403);
service.GetAll().then(function(error) {
expect(error.success).toBe(false);
expect(error.message).toEqual('Error getting all users');
});
$httpBackend.flush();
});
});
You can see it working on Plunker: https://plnkr.co/edit/agV0rO?p=preview
I have a controller with an activate() method:
function CustomerController(customerFactory, $state, $stateParams) {
var vm = this;
vm.getCustomer = getCustomer;
activate();
function activate() {
if($state.is('customer-view')) vm.getCustomer($stateParams.id);
}
function getCustomer(id) {
return customerFactory.getCustomer(id)
.then(getCustomerComplete)
.catch(getCustomerFailed);
function getCustomerComplete(response) {
return vm.customer = response;
}
function getCustomerFailed(error) {
return vm.error = error;
}
}
}
I want to test, using jasmine, that on $state.go('customer-view', {id: 1}) the getCustomer(id) function has been called.
Here is my test:
it("should be called", inject(function($state) {
spyOn(vm, 'getCustomer');
$state.go('customer-view', {id: 1});
expect(vm.getCustomer).toHaveBeenCalledTimes(1);
}));
And the test returns "Expected spy getCustomer to have been called once. It was called 0 times."
PS: the code above is working, I just don't know how to test it.
EDIT:
Here a bigger example of my spec file:
describe("app.customers controller", function () {
var $controller, customers, deferred, customerFactoryMock, vm, $rootScope, $q;
beforeEach(function () {
module('app.customers');
});
beforeEach(inject(function (_$rootScope_, _$controller_, _$q_, _customerFactory_) {
customerFactoryMock = _customerFactory_;
$controller = _$controller_;
vm = $controller("CustomerController", {customerFactory: customerFactoryMock});
$q = _$q_;
deferred = $q.defer();
$rootScope = _$rootScope_;
customers = [{"id": 1}, {"id": 2}];
}));
describe("getCustomer(id)", function () {
it("should call the factory method", function () {
spyOn(customerFactoryMock, 'getCustomer').and.returnValue(deferred.promise);
vm.getCustomer(1);
expect(customerFactoryMock.getCustomer).toHaveBeenCalledTimes(1);
});
it("should be called", inject(function($state) {
spyOn(vm, 'getCustomer');
$state.go('customer-view', {id: 1});
expect(vm.getCustomer).toHaveBeenCalledTimes(1);
}));
it("should have a customer on resolved", function () {
spyOn(customerFactoryMock, 'getCustomer').and.returnValue(deferred.promise);
deferred.resolve({"id": 1});
vm.getCustomer(1);
$rootScope.$digest();
expect(vm.customer).toBeDefined();
expect(vm.customer.id).toEqual(1);
});
it("should return an error message on reject", function () {
spyOn(customerFactoryMock, 'getCustomer').and.returnValue(deferred.promise);
deferred.reject('this is a reason');
vm.getCustomer();
$rootScope.$digest();
expect(vm.error).toBeDefined();
expect(vm.error).toBe('this is a reason');
});
});
});
I'm trying to mock a service I'm using and should return a promise, the mock service is being called but I can't get the result to my test.
service function to be tested:
function getDevices() {
console.log('getDevices');
return servicesUtils.doGetByDefaultTimeInterval('devices')
.then(getDevicesComplete);
function getDevicesComplete(data) {
console.log('getDevicesComplete');
var devices = data.data.result;
return devices;
}
}
My test is:
describe('devicesService tests', function () {
var devicesService;
var servicesUtils, $q, $rootScope;
beforeEach(function () {
servicesUtils = {};
module('app.core', function ($provide) {
servicesUtils = specHelper.mockServiceUtils($provide, $q, $rootScope);
});
inject(function (_devicesService_, _$q_, _$rootScope_) {
devicesService = _devicesService_;
$q = _$q_;
$rootScope = _$rootScope_.$new();
});
});
it('getting device list', function () {
console.log('getting device list');
devicesService.getDevices().then(function (result) {
console.log(result);
expect(result).toBeDefined();
});
});
});
Mock file:
function mockServiceUtils($provide, $q) {
var servicesUtils = {};
servicesUtils.doGetByDefaultTimeInterval = jasmine.createSpy().and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
$rootScope.$digest();
return deferred.promise;
});
$provide.value('servicesUtils', servicesUtils);
return servicesUtils;
}
Your code is way too complex.
Let's assume that you want to test a service devicesService that uses another service servicesUtils, having a method that returns a promise.
Let's assume devicesService's responsibility is to call servicesUtils and transform its result.
Here's how I would do it:
describe('devicesService', function() {
var devicesService, servicesUtils;
beforeEach(module('app.core'));
beforeEach(inject(function(_devicesService_, _servicesUtils_) {
devicesService = _devicesService_;
servicesUtils = _servicesUtils_;
}));
it('should get devices', inject(function($q, $rootScope) {
spyOn(servicesUtils, 'doGetByDefaultTimeInterval').and.returnValue($q.when('Remote call result'));
var actualResult;
devicesService.getDevices().then(function(result) {
actualResult = result;
});
$rootScope.$apply();
expect(actualResult).toEqual('The transformed Remote call result');
}));
});