I have a controller which relies on a service built through ngResource. I am having trouble testing this(although both appear to work like a charm in the actual application). The following is the (sanitized) Controller
MyApp.Controller.MyCarsController = (scope, http, typeService) ->
if scope.context==undefined
scope.ferrari_or_porshe =""
scope.id = ""
else if scope.context=="ferrari"
scope.country_or_pi ="Ferrari"
else if scope.context=="porshe"
scope.country_or_pi ="Porshe"
typeService.index
ferrari_or_porshe: scope.ferrari_or_porshe
id: scope.id
, (response) ->
scope.type = response
scope.loading = false
MyApp.Controller.MyCarsController.$inject = ['$scope', '$http', 'Type']
And this is the Service:
MyApp.MyModule.factory 'Type', ['$resource', ($resource) ->
TypeResource = $resource("/api/types/:ferrari_or_porshe/:id", {},
index:
method: "GET"
isArray: true
)
return TypeResource
]
Finally, some test code:
describe 'MyApp.Controller.MyCarsController', ->
beforeEach module('MyModule')
beforeEach inject ($rootScope, $http, $controller, Type) ->
@scope = $rootScope.$new()
@typeService = Type
@scope.context = undefined
$controller 'MyApp.Controller.MyCarsController', $scope: @scope
describe '#home-page', ->
it 'contains a list of types', ->
expect(@scope.types.length).toBeGreaterThan 0
it "sets instance variables correctly", ->
expect(@scope.ferrari_or_porshe).toBe ""
expect(@scope.id).toBe ""
Which fails with:
No more request expected in helpers/angular-mocks.js on line 889
TypeError: 'undefined' is not an object (evaluating 'this.scope.types.length') in controllers/my_cars_controller_spec.js
By judicious application of console.logs, I have discovered that the issue is that the final callback on response is never reached. TypeResource comes back as [Function].
My questions is:
How do I drive the Jasmine Tests to correctly enter the Service and fetch a response? And is there any way to create direct Unit Tests for Services?
Any and all help is appreciated
The Solution is as follows: for the Service, use $httpBackend which is bundled as part of ngMock:
Use this to mock the Rest responses. Since in my case I only cared about verifying that a GET request goes out:
describe 'Type', ->
describe '#index', ->
beforeEach module('MyModule')
beforeEach inject(($httpBackend) ->
$httpBackend.whenGET('/api/types/ferrari/1').respond([])
)
it 'for a ferrari scope', inject((Type) ->
ferrari_or_porsche = 'ferrari'
id = '1'
expect( Type.index('ferrari_or_porsche': ferrari_or_porsche, 'id': id) ).toEqual([ ])
)
And then for the controller, mock the service using jasmine spies and use jasmine.any(Function)
to warn of the callback.
describe 'MyApp.Controller.MyCarsController', ->
beforeEach module('myModule')
beforeEach inject ($rootScope, $http, $controller, Type) ->
@scope = $rootScope.$new()
@typeService = Type
@scope.context = undefined
spyOn(@typeService, 'index')
describe '#home-page', ->
beforeEach inject ($controller) ->
$controller 'MyApp.Controller.MyCarsController', $scope: @scope
it 'contains a list of types', ->
expect(@typeService.index).toHaveBeenCalledWith({ ferrari_or_porsche : '', id : '' }, jasmine.any(Function))
it "sets instance variables correctly", ->
expect(@scope.ferrari_or_porsche).toBe ""
expect(@scope.id).toBe ""
Note: I make no claims as to the "canonicalness" of this solution. But it works. Note: The API endpoints are of course tested extensively elsewhere.