AngularJS Jasmine 2.0 async testing timed out
-
23-12-2019 - |
سؤال
I know that this has been asked many times, and I have look at other questions and follow them but I can't seem to resolve this issue.
Basically, I have a function in a Service to put a data into a pouchDB. The function addTask
will return a promise that will resolve as the result value when the database insertion succeed.
This works fine during manual testing in browser environment, but fail during Jasmine test due to timeout.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
It seems that the callback which passed as an argument in the then
of the spec never runs.
app = angular.module 'testApp', ['ngMock']
app.service 'Pouch', ($q) ->
db = new PouchDB 'tasks'
return {
addTask : (task) ->
deferred = $q.defer()
db.put task, (task.title + task.due), (err, res) ->
console.log res # Both prints fine
console.log err
deferred.resolve res
return deferred.promise
}
describe 'Service: Pouch', ->
Pouch = {}
$rootScope = {}
beforeEach () ->
module 'testApp'
PouchDB.destroy 'tasks'
inject (_Pouch_, _$rootScope_) ->
Pouch = _Pouch_
$rootScope = _$rootScope_
value = undefined
testTask =
type: 'TASK'
title: 'Feed the kitten'
due: 201120141900
group: ['TODAY', 'TOMORROW']
it 'should add task upon request', (done) ->
promise = Pouch.addTask testTask
promise.then (result) ->
# Never reached here
expect(result.ok).toBe(true)
done()
$rootScope.$apply() # I don't think this is neccessary.
What should I do? I tried using $timeout
too, but it didn't work.
المحلول
This looks one of two things I can think of:
- Either: you're not testing your Pouch service in isolation it seems. What is that asynchronous call going? What is it doing? Is there something you can mock and inject into it so you can use
$q.when()
and$timeout.$flush()
to test? - ...or... you mean this to be an integration test, in which case you can set
jasmine. DEFAULT_TIMEOUT_INTERVAL
to a higher number for that one test usingbeforeEach
andafterEach
EDIT: Your service, as written in your post and the accepted answer cannot be tested in isolation.
You need to inject PouchDB as a dependency if you want to test this in isolation. You don't need to test Pouch, just your own code:
# Create a service that gets PouchDB from $window
app.service 'PouchDB', ($window) ->
return $window.PouchDB
# Now you can inject it as a dependency!
app.service 'Pouch', ($q, $rootScope, PouchDB) ->
# *now* new it up.
db = new PouchDB 'tasks'
return addTask: (task) ->
deferred = $q.defer()
db.put task, (task.title + task.due), (err, res) ->
$rootScope.$apply -> deferred.resolve res
deferred.promise
And your tests are now isolated:
describe 'Service: Pouch', ->
Pouch = undefined
$rootScope = undefined
PouchDBMock = undefined
$timeout = undefined
value = undefined
beforeEach () ->
module 'testApp', ($provide) ->
# create your mock
PouchDBMock = jasmine.createSpyObj('PouchDB', ['put', 'get', 'etc']);
# provide it to the container
$provide.value 'PouchDB', PouchDBMock
# No longer necessary, because you're isolated!
# ---> PouchDB.destroy 'tasks' <---
# Now when you inject your Pouch service, it will have
# our PouchDBMock being used inside of it.
# also inject $timeout so you can flush() later
inject (_Pouch_, _$rootScope_, _$timeout_) ->
Pouch = _Pouch_
$rootScope = _$rootScope_
$timeout = _$timeout_
# MOVED: this should be initialized in your beforeEach or your it, not in the body
# of define... that's bad. Every new `it` test could mutate this and it won't reset
# in the beforeEach where you had it.
testTask =
type: 'TASK'
title: 'Feed the kitten'
due: 201120141900
group: ['TODAY', 'TOMORROW']
it 'should add task upon request', (done) ->
# tell the spy on `put` to return something distinct
PouchDBMock.put.andReturn('something special');
# call the method
promise = Pouch.addTask testTask
# handle the promise
promise.then (result) ->
# assert put was called on your mock
expect(PouchDBMock.put).toHaveBeenCalledWith(testTask)
# assert the result was what you expected (the return from the spy)
expect(result).toBe('something special')
# and you're done... even though this wasn't asynchronous
done()
# flush all unresolved promises
$timeout.flush()
نصائح أخرى
The issue is that since the pouchdb callback happens outside of Angular's digest cycle, you have to call $rootScope.$apply()
inside the pouchdb callback function.
app.service 'Pouch', ($q, $rootScope) -> db = new PouchDB 'tasks'
return {
addTask : (task) ->
deferred = $q.defer()
db.put task, (task.title + task.due), (err, res) ->
deferred.resolve res
$rootScope.$apply()
return deferred.promise
}
Most likely the issue is with PhantomJS, which requires a shim for Function.prototype.bind
in order to work properly with PouchDB. You can get such a shim from https://github.com/es-shims/es5-shim.