سؤال

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.

JSBin

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:

  1. 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?
  2. ...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 using beforeEach and afterEach

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.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top