Getting error when trying to use $provide in jasmine unit test - angularjs

I'm using AngularJS and trying to test a controller that calls a factory to get some data.
This is the controller code:
'use strict'
angular.module('AngularApp')
.controller 'IndexCtrl', ($scope, session, navigation) ->
session.find().then (response) ->
$scope.session = response.data
$scope.someOtherVariable = {}
Naturally, I'd like to swap the factory with a mock to prevent calling a real API. I'm trying to use $provide.factory to inject the mock copy:
'use strict'
describe 'Controller: IndexCtrl', ->
# load the controller's module
beforeEach module 'mosaicAdminWebClientApp'
beforeEach module ($provide) ->
$provide.factory 'session', ->
true
IndexCtrl = {}
scope = {}
# Initialize the controller and a mock scope
beforeEach inject ($controller, $rootScope) ->
scope = $rootScope.$new()
IndexCtrl = $controller 'IndexCtrl', {
$scope: scope
}
it 'should attach a list of awesomeThings to the scope', ->
expect(true).toBe true
When running this test with Karma, I guess this error:
Chrome 32.0.1700 (Mac OS X 10.9.1) Controller: IndexCtrl should attach a list of awesomeThings to the scope FAILED
Error: [ng:areq] Argument 'fn' is not a function, got Object
http://errors.angularjs.org/1.2.10-build.2176+sha.e020916/ng/areq?p0=fn&p1=not%20a%20function%2C%20got%20Object
at /Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:78:12
at assertArg (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:1363:11)
at assertArgFn (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:1373:3)
at annotate (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3019:5)
at Object.invoke (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3685:21)
at /Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3554:71
at Array.forEach (native)
at forEach (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:303:11)
at Object.createInjector [as injector] (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular/angular.js:3554:3)
at workFn (/Users/blaiz/Documents/some_angular_app/app/bower_components/angular-mocks/angular-mocks.js:2144:52)
Chrome 32.0.1700 (Mac OS X 10.9.1): Executed 10 of 10 (1 FAILED) (0.26 secs / 0.068 secs)
Warning: Task "karma:unit" failed. Use --force to continue.
Aborted due to warnings.
I've tried many different permutation, such as removing the label, passing an object or simple value instead of a function and none of them worked.
The documentation (http://docs.angularjs.org/api/AUTO.$provide) shows that I should call the factory() method with as factory(name, $getFn) where name is a string and $getFn is a function. That's what I'm doing but it's not working.
Anything I've missed? Does anyone know how to properly use $provide in Jasmine unit tests?
Thanks
Update:
I found a plunkr similar that solved an issue similar to this one here: stackoverflow.com/questions/19297258/why-is-provide-only-available-in-the-angular-mock-module-function-and-q-onl
I created my own plunkr with this code, but with the test code in JS instead of Coffee and got it to work: plnkr.co/edit/gfBzMXpKdJgPKnoyJy5A?p=preview
Now I manually converted the JS code into Coffee: plnkr.co/edit/qGGMayFjJoeYZyFKPjuR?p=preview
The error is back so the coffee code is wrong but the js code works.

Found the answer to my question.
Since CoffeeScript always returns the result from the last statement, in this case, CoffeeScript returns $provide inside of module(), which is wrong. An easy way to fix that is to just manually add a return statement after $provide, like this:
beforeEach module 'mosaicAdminWebClientApp', ($provide) ->
$provide.service 'session', ()->
return
Hope that will help someone else.

Related

How to mock Angular modules before injecting?

I have the following dependencies in my application:
angular.module 'MyApplication.NetworkingModule', ['ngResource']
.value 'NetworkingValues'
angular.module 'MyApplication.MyModule', []
.factory 'MySpecificResource', ['NetworkingValues', ...]
.factory 'MyService', ['MySpecificResource', ...]
So MySpecificResource depends on NetworkingValues.
The thing is I'd like to mock some of these values before using them in resources in tests.
Doing this:
beforeEach module 'MyApplication.NetworkingModule'
beforeEach module 'MyApplication.MyModule'
causes that factory is invoked immediately and I don't have a chance to replace some of NetworkingValues.
beforeEach inject (#MyService, #NetworkingValues) ->
MyService depends on MySpecificResource, which depends on NetworkingValues that I want to stub before tests.
What are the correct way to do that?
Have you tried something along these lines?
var mockedValues;
beforeEach(module(function($provide) {
$provide.value('NetworkingValues',mockedValues);
}));
beforeEach(inject(function(_NetworkingValues_) {
mockedValues = {....};
}));
The $provide will supply your mockedValues instead of the actual NetworkingValues.
So my final solution was to provide constant and inject it together with module function:
beforeEach module "MyApplication.NetworkingModule", ($provide) ->
$provide.constant 'NetworkingValues', endpointGet: 'my-endpoint/:id'

How to mock AngularJS $resource with $promise.then in jasmine specs

I use $resource to set up some API calls, and while testing I have adopted the general approach of injecting $qand then doing
mockMyService.doSomethingAsync.andReturnValue($q.when(successResponse))
This has been working out pretty well, however, I have a method that looks like the following:
# MyService
MyService.doSomethingAsync(params).$promise.then ->
$scope.isLoading = false
# MyService Spec
mockMyService =
doSomethingAsync: jasmine.createSpy('doSomethingAsync')
it 'calls service #doSomethingAsync', ->
inject ($q) -> mockMyService.doSomethingAsync.and.returnValue($q.when(response))
controller.methodThatWrapsServiceCall()
scope.$apply()
expect(mockMyService.doSomethingAsync).toHaveBeenCalled()
and unfortunately the mocking strategy outlined above doesn't seem to work when a $promise.then is chained at the end. I end up with the following error:
TypeError: 'undefined' is not an object (evaluating 'MyService.doSomethingAsync(params).$promise.then')
Methods that simply end with doSomethingAsync().$promise pass the tests without issues using this mocking strategy.
(Other info: These are Jasmine tests run with Karma and PhantomJS)
Actually, figured it out!
I just changed my mock to return and.returnValue( { $promise: $q.when(response) } )

Mock with jasmine

I want to isolate my angular unit specs.
My code base consists of a factory and a controller:
.factory('ServiceData', ->
{ message: 'im data from a factory service' }
)
.controller('Controller', ['$scope','ServiceData',($scope, ServiceData)->
$scope.serviceData = ServiceData
And here is a spec for the controller:
it 'should get data from a service', ->
spyOn(ServiceData, 'message').andReturn('im from a mock') # this doesn't work
expect($scope.serviceData).toEqual 'im from a mock'
The spec doesn't work, because I get the following error:
ReferenceError: ServiceData is not defined in ...controller_spec.js
I want to isolate my controller spec from my factory. How do I achieve this?
Also, this is a trivial example, but in future I will have very complex factories that get data from the server and all sorts. So I want to know how to stub them out.
I also might want to test how my controller behaves based on different things that are returned by the mocked out function, so I definitely want to use this approach.
I've tried to using $provide, but to much success
beforeEach ->
module 'App', ($provide)->
$provide.value 'serviceData', { message: 'im from a mock' }
results in:
Error: [ng:areq] Argument 'fn' is not a function, got Object
I mean...I just don't even know
Quote:
it 'should get data from a service', ->
spyOn(ServiceData, 'message').andReturn('im from a mock') # this doesn't work
expect($scope.serviceData).toEqual 'im from a mock'
The spec doesn't work, because I get the following error:
ReferenceError: ServiceData is not defined in ...controller_spec.js
/quote
I think it should be something more like this:
it 'should get data from a service', -> //this is coffeescript syntax you're using I assume
$scope.serviceData = ServiceData.serviceData
expect($scope.serviceData).toEqual 'im from a service'
//where ServiceData is defined as a service that returns a Plain Old Javascript Object
//and that service returns an object in format: { serviceData: 'im from a service' }
If I understand what you are trying to test

How do I spy on an Angular promise chain using Jasmine

Using AngularJS, CoffeeScript and Jasmine (edited in WebStorm), I would like to unit test a chain of promises.
Lets say I have the following example service:
Angular Service
class ExampleService
stepData: []
constructor: (#$http) ->
attachScopeMethod: (#scope) ->
#scope.callSteps = => #step1().then -> #step2()
step1: ->
#$http.get('app/step/1').then (results) =>
#stepData[0] = results.data
results
step2: ->
#$http.get('app/step/2').then (results) =>
#stepData[2] = results.data
results
This service allows me to attach a the method callSteps() to the scope. This method, when called, executes a series of asynch $http calls to a 3rd party API.
To test that each step is at least called, I have written the following Jasmine spec.
Jasmine Spec
ddescribe 'ExampleService', ->
beforeEach ->
module 'myApp'
beforeEach inject ($rootScope, $injector) ->
#scope = $rootScope.$new()
#exampleService = $injector.get 'exampleService'
#q = $injector.get '$q'
describe 'process example steps', ->
beforeEach ->
#exampleService.attachScopeMethod(#scope)
it "should attach the scope method", ->
expect(#scope.callSteps).toBeDefined()
describe 'when called should invoke the promise chain', ->
it "should call step1 and step2", ->
defer = #q.defer()
#exampleService.step1 = jasmine.createSpy('step1').andReturn(defer.promise)
#exampleService.step2 = jasmine.createSpy('step2')
#scope.callSteps()
defer.resolve()
expect(#exampleService.step1).toHaveBeenCalled()
expect(#exampleService.step2).toHaveBeenCalled()
The results of this test is as follows:
expect(#exampleService.step1).toHaveBeenCalled() - PASS
expect(#exampleService.step2).toHaveBeenCalled() - FAIL
Can you tell me how I can get step2() to succesfully run under test?
Thank you
EDIT
#Dashu below kindly supplied the answer to the problem. The trick is to simply call scope.$apply or scope.$digest to trigger the promise chain resolution.
So here is the working test fragment.
describe 'when called should invoke the promise chain', ->
it "should call step1 and step2", ->
defer = #q.defer()
defer.resolve()
#exampleService.step1 = jasmine.createSpy('step1').andReturn(defer.promise)
#exampleService.step2 = jasmine.createSpy('step2')
#scope.callSteps()
#scope.$apply()
expect(#exampleService.step1).toHaveBeenCalled()
expect(#exampleService.step2).toHaveBeenCalled()
try a $rootScope.$apply() before the second expect
also about defer.resolve(). i don't know if this actually resolves the promise, i think it just setups the value to return when it resolves.
so i would move that up to just underneath the $q.defer() call, before passing the promise to the andReturn()
you could do defer.resolve(true), defer.reject(false), so if you're promise will get rejected insinde callsteps, true or false will be returned

Getting "Unknown provider: $qProvider <- $q" when fetching the defer/promise from my AngularJS injector

I am trying to create a simple example where I get the promise/defer object from AngularJS's service solution:
var $q;
function init() {
var $injector = window.angular.injector();
console.log($injector);
$injector.invoke(["$q", function (_$q) {
console.log($q);
$q = _$q;
}]);
}
init();
But it results in:
Error: Unknown provider: $qProvider <- $q
[Break On This Error]
throw Error("Unknown provider: " + path.join(' <- '));
What could I have missed?
You have to add which module the provider resides in like this:
var $injector = window.angular.injector(['ng']);
Then it will work!
Edit: Regarding the 'ng' module, the docs specifically says that it must be explicitly added. From the angular injector docs:
modules – {Array.<string|Function>} – A list of module functions or their aliases.
See angular.module. The ng module must be explicitly added.
Use https://github.com/kriskowal/q if you're outside a angular.js module lifecycle, I've go down that road and it will not work well, $q is not meant to be used outside of a bootstraped module (require a $rootScope).

Resources