Jasmine - How do I mock a finally call? - javascript

I have a test that creates a controller like such...
this.createScope = function(scope) {
if (scope) {
this.scope = scope;
} else {
this.scope = $rootScope.$new();
}
this.controller = $controller("menuController", {
"$scope": this.scope,
updateActionList: function() {
return {
finally: function() {}
};
}
});
};
I added this part...
updateActionList: function() {
return {
finally: function() {}
};
}
Because when I run my tests, all of them fail because....
TypeError: undefined is not an object (evaluating 'updateActionList().finally')
updateActionList() is a local function that is called in the actual code like this...
updateActionList().finally(function() {
//Do stuff
});
updateActionList() returns a promise from getThings() with a .then and a .finally blocks.
I just want the finally block to resolve itself really so the tests can pass.
Or is there some other thing I need to do? I'm not sure why the finally is undefined.

So this call...
updateActionList().finally(function() {
//Do stuff
});
returns a promise from some other function, essentially my problem was that the function returning the promise before updateActionList() mock needed another finally call.
this.getThings = jasmine.createSpy("getThings").and.returnValue({
then: function(cb) {
cb(self.mockPlugins);
return {
finally: function(cb) {
cb();
return {
finally: function(cb) {
cb();
}
};
}
};
}
});

Related

Jasmine - How to chain a `.then` and a `.finally` in a callFake spy?

I have the following function, that does a service call with a promise and a .finally:
myService.getStuff().then(function() {
this.doStuffWhenServiceOK();
}, function () {
this.doStuffWhenServiceFails();
}).finally(function() {
this.doFinally();
});
I am spying on this service with the following spy:
spyOn(myService, 'getStuff').and.callFake(function() {
return {
then: function (succesFn, errorFn) {
return succesFn();
}
};
});
The problem is that the test complains that the .finally is not known. Just adding it after .then does not seem to be a solution...
return {
then: function(successFn) {
return successFn();
},
finally: function(successFn) {
return successFn();
}
}
Who knows how to chain .then and .finally in the callFake spy?
I work with Angular 1.
Return a finally function.
function then(succesFn, errorFn) {
succesFn();
return {finally:function() {}};
}

AngularJS $scope variable change in .then() unit testing

I'm trying to unit test a function within my controller but am unable to get a $scope variable to be testable. I'm setting the variable in my controller's .then() and want to unit test to make sure this is set appropriately when it hits the .then block.
My test controller code:
function submit() {
myService.submit().then(function(responseData){
if(!responseData.errors) {
$scope.complete = true;
$scope.details = [
{
value: $scope.formattedCurrentDate
},
{
value: "$" + $scope.premium.toFixed(2)
},
];
} else {
$scope.submitError = true;
}
});
}
Where this service call goes is irrelevant. It will return JSON with action: 'submitted', 'response' : 'some response'. The .then() checks if errors are present on responseData, and if not it should set some details. These $scope.details are what I'm trying to test in my unit test below:
it('should handle submit details', function () {
var result;
var premium = 123.45;
var formattedCurrentDate = "2016-01-04";
var promise = myService.submit();
mockResponse = {
action: 'submitted',
response: 'some response'
};
var mockDetails = [
{
value: formattedCurrentDate
},
{
value: "$"+ premium.toFixed(2)
}
];
//Resolve the promise and store results
promise.then(function(res) {
result = res;
});
//Apply scope changes
$scope.$apply();
expect(mockDetails).toEqual(submitController.details);
});
I'm receiving an error that $scope.details is undefined. I'm not sure how to make the test recognize this $scope data changing within the controller.
Before each and other functions in my unit test:
function mockPromise() {
return {
then: function(callback) {
if (callback) {
callback(mockResponse);
}
}
}
}
beforeEach(function() {
mockResponse = {};
module('myApp');
module(function($provide) {
$provide.service('myService', function() {
this.submit = jasmine.createSpy('submit').and.callFake(mockPromise);
});
});
inject(function($injector) {
$q = $injector.get('$q');
$controller = $injector.get('$controller');
$scope = $injector.get('$rootScope');
myService = $injector.get('myService');
submitController = $controller('myController', { $scope: $scope, $q : $q, myService: myService});
});
});
How do I resolve the promise within my unit test so that I can $scope.$digest() and see the $scope variable change?
You should look how to test promises with jasmine
http://ng-learn.org/2014/08/Testing_Promises_with_Jasmine_Provide_Spy/
using a callFake would do what you try to mock
spyOn(myService, 'submit').and.callFake(function() {
return {
then: function(callback) { return callback(yourMock); }
};
});

Unit testing Angular filter which relies on service

I'm attempting to test a custom filter I've built. The issue I'm running into is that this filter relies on an asynchronous call through a service. Below is my relevant filter code first, then my current test:
.filter('formatValue', ['serverService', '_', function(serverService, _) {
var available = null;
var serviceInvoked = false;
function formatValue(value, code) {
var details = _.findWhere(available, {code: code});
if (details) {
return details.unitSymbol + parts.join('.');
} else {
return value;
}
}
getAvailable.$stateful = true;
function getAvailable(value, code) {
if (available === null) {
if (!serviceInvoked) {
serviceInvoked = true;
serverService.getAvailable().$promise.then(function(data) {
available = data;
});
}
} else {
return formatValue(value, code);
}
}
return getAvailable;
}])
test:
describe('filters', function() {
beforeEach(function() {
module('underscore');
module('gameApp.filters');
});
beforeEach(module(function($provide) {
$provide.factory('serverService', function() {
var getAvailable = function() {
return {
// mock object here
};
};
return {
getAvailable: getAvailable
};
});
}));
describe('formatValue', function() {
it('should format values', inject(function(formatValueFilter) {
expect(formatValueFilter(1000, 'ABC')).toEqual('å1000');
}));
});
});
The error I'm encountering when running my tests is:
TypeError: 'undefined' is not an object (evaluating 'serverService.getAvailable().$promise.then')
Your mock service needs to return a resolved promise. You can do this by injecting $q and returning $q.when(data)
However, I would think about refactoring this filter first. Filters are intended to be fast computations and probably should not be dependent on an asynchronous call. I would suggest moving your http call to a controller, then pass in the data needed to the filter.

How do I mock a service that returns promise in AngularJS Jasmine unit test?

I have myService that uses myOtherService, which makes a remote call, returning promise:
angular.module('app.myService', ['app.myOtherService'])
.factory('myService', [
myOtherService,
function(myOtherService) {
function makeRemoteCall() {
return myOtherService.makeRemoteCallReturningPromise();
}
return {
makeRemoteCall: makeRemoteCall
};
}
])
To make a unit test for myService I need to mock myOtherService, such that its makeRemoteCallReturningPromise method returns a promise. This is how I do it:
describe('Testing remote call returning promise', function() {
var myService;
var myOtherServiceMock = {};
beforeEach(module('app.myService'));
// I have to inject mock when calling module(),
// and module() should come before any inject()
beforeEach(module(function ($provide) {
$provide.value('myOtherService', myOtherServiceMock);
}));
// However, in order to properly construct my mock
// I need $q, which can give me a promise
beforeEach(inject(function(_myService_, $q){
myService = _myService_;
myOtherServiceMock = {
makeRemoteCallReturningPromise: function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
}
};
}
// Here the value of myOtherServiceMock is not
// updated, and it is still {}
it('can do remote call', inject(function() {
myService.makeRemoteCall() // Error: makeRemoteCall() is not defined on {}
.then(function() {
console.log('Success');
});
}));
As you can see from the above, the definition of my mock depends on $q, which I have to load using inject(). Furthermore, injecting the mock should be happening in module(), which should be coming before inject(). However, the value for the mock is not updated once I change it.
What is the proper way to do this?
I'm not sure why the way you did it doesn't work, but I usually do it with the spyOn function. Something like this:
describe('Testing remote call returning promise', function() {
var myService;
beforeEach(module('app.myService'));
beforeEach(inject( function(_myService_, myOtherService, $q){
myService = _myService_;
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});
}
it('can do remote call', inject(function() {
myService.makeRemoteCall()
.then(function() {
console.log('Success');
});
}));
Also remember that you will need to make a $digest call for the then function to be called. See the Testing section of the $q documentation.
------EDIT------
After looking closer at what you're doing, I think I see the problem in your code. In the beforeEach, you're setting myOtherServiceMock to a whole new object. The $provide will never see this reference. You just need to update the existing reference:
beforeEach(inject( function(_myService_, $q){
myService = _myService_;
myOtherServiceMock.makeRemoteCallReturningPromise = function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
};
}
We can also write jasmine's implementation of returning promise directly by spy.
spyOn(myOtherService, "makeRemoteCallReturningPromise").andReturn($q.when({}));
For Jasmine 2:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue($q.when({}));
(copied from comments, thanks to ccnokes)
describe('testing a method() on a service', function () {
var mock, service
function init(){
return angular.mock.inject(function ($injector,, _serviceUnderTest_) {
mock = $injector.get('service_that_is_being_mocked');;
service = __serviceUnderTest_;
});
}
beforeEach(module('yourApp'));
beforeEach(init());
it('that has a then', function () {
//arrange
var spy= spyOn(mock, 'actionBeingCalled').and.callFake(function () {
return {
then: function (callback) {
return callback({'foo' : "bar"});
}
};
});
//act
var result = service.actionUnderTest(); // does cleverness
//assert
expect(spy).toHaveBeenCalled();
});
});
You can use a stubbing library like sinon to mock your service. You can then return $q.when() as your promise. If your scope object's value comes from the promise result, you will need to call scope.$root.$digest().
var scope, controller, datacontextMock, customer;
beforeEach(function () {
module('app');
inject(function ($rootScope, $controller,common, datacontext) {
scope = $rootScope.$new();
var $q = common.$q;
datacontextMock = sinon.stub(datacontext);
customer = {id:1};
datacontextMock.customer.returns($q.when(customer));
controller = $controller('Index', { $scope: scope });
})
});
it('customer id to be 1.', function () {
scope.$root.$digest();
expect(controller.customer.id).toBe(1);
});
using sinon :
const mockAction = sinon.stub(MyService.prototype,'actionBeingCalled')
.returns(httpPromise(200));
Known that, httpPromise can be :
const httpPromise = (code) => new Promise((resolve, reject) =>
(code >= 200 && code <= 299) ? resolve({ code }) : reject({ code, error:true })
);
Honestly.. you are going about this the wrong way by relying on inject to mock a service instead of module. Also, calling inject in a beforeEach is an anti-pattern as it makes mocking difficult on a per test basis.
Here is how I would do this...
module(function ($provide) {
// By using a decorator we can access $q and stub our method with a promise.
$provide.decorator('myOtherService', function ($delegate, $q) {
$delegate.makeRemoteCallReturningPromise = function () {
var dfd = $q.defer();
dfd.resolve('some value');
return dfd.promise;
};
});
});
Now when you inject your service it will have a properly mocked method for usage.
I found that useful, stabbing service function as sinon.stub().returns($q.when({})):
this.myService = {
myFunction: sinon.stub().returns( $q.when( {} ) )
};
this.scope = $rootScope.$new();
this.angularStubs = {
myService: this.myService,
$scope: this.scope
};
this.ctrl = $controller( require( 'app/bla/bla.controller' ), this.angularStubs );
controller:
this.someMethod = function(someObj) {
myService.myFunction( someObj ).then( function() {
someObj.loaded = 'bla-bla';
}, function() {
// failure
} );
};
and test
const obj = {
field: 'value'
};
this.ctrl.someMethod( obj );
this.scope.$digest();
expect( this.myService.myFunction ).toHaveBeenCalled();
expect( obj.loaded ).toEqual( 'bla-bla' );
The code snippet:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
});
Can be written in a more concise form:
spyOn(myOtherService, "makeRemoteCallReturningPromise").and.returnValue(function() {
return $q.resolve('Remote call result');
});

Mocked service returning Object ($$state) error

I have been following the angular testing Play by Play on PluralSight by John Papa and Ward Bell.
I'm currently getting the following error when I run my specs.
AssertionError: expected { Object ($$state) } to have a property 'length'
at Assertion.assertLength (bower_components/chai/chai.js:1331:37)
at Assertion.assert (bower_components/chai/chai.js:4121:49)
at Context.<anonymous> (scripts/home/homeController.Specs.js:48:49)
Note that I have only included the code that I think is relevant so that I am not overloading this question with irrelevant information. If you need to see more code it's not a problem.
My code is as follows:
homeController.js:
window.app.controller('homeController', ['$scope', 'sidebarService',
function ($scope, sidebarService) {
$scope.title = 'Slapdash';
$scope.sidebar = {
"items": sidebarService.getSidebarItems()
};
}])
sidebarService.js:
(function () {
window.app
.service('sidebarService',['$http', function ($http) {
this.getSidebarItems = function () {
$http.get("http://wwww.something.com/getSidebarItems")
.then(function (response) {
return response.data;
});
};
}]);
}());
homeController.Specs.js:
beforeEach:
beforeEach(function () {
bard.appModule('slapdash');
bard.inject(this, '$controller', '$q', '$rootScope')
var mockSidebarService = {
getSidebarItems : function(){
return $q.when(mockSidebarMenuItems);
}
};
controller = $controller('homeController', {
$scope: scope,
sidebarService: mockSidebarService
});
});
failing spec:
it('Should have items', function () {
$rootScope.$apply();
expect(scope.sidebar.items).to.have.length(mockSidebarMenuItems.length); // same number as mocked
expect(sidebarService.getSidebarItems).to.have.been.calledOnce; // it's a spy
});
The answer was that I was returning a result from the service not a promise.
$http.get("http://wwww.something.com/getSidebarItems")
.then(function (response) {
return response.data; // <- returning data not promise
});
When I was mocking I was using
var mockSidebarService = {
getSidebarItems : function(){
return $q.when(mockSidebarMenuItems);
}
};
which mocks a promise. However I just needed to return the data as the promise was awaited in the service.
mockSidebarService = {
getMenuItems : function(){
return mockSidebarMenuItems
}
};
I made the changes and it all works now. Took a while but at least it's making sense.

Categories