2

Chai as Promised documentation states as follows:

Notice: either return or notify(done) must be used with promise assertions.

And the examples on the site are as follows:

return doSomethingAsync().should.eventually.equal("foo");

doSomethingAsync().should.eventually.equal("foo").notify(done);

The thing is; I actually wrote a test using chai as promised without returning the promise. Like so:

it('should resolve user', function () {
    $state.get(state).resolve.user(dataservice, {
      userId: testUser.id
    }).should.eventually.eq(testUser);
    $rootScope.$apply();
  });

And it works perfectly fine. I am sure it does as I change testUser to something else the test fails. Just like I expected. So I am not sure if I am doing something wrong here.

In fact, when I modified the code to return a promise, it failed with error "Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test." The modified code is below:

it('should resolve user', function () {
    var promise = $state.get(state).resolve.user(dataservice, {
      userId: testUser.id
    }).should.eventually.eq(testUser);
    $rootScope.$apply();
    return promise;
  });

A little confused here. It might have something to do with Angular $q. To make it clear, the function resolve.user returns a $q promise.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Bren
  • 2,148
  • 1
  • 27
  • 45
  • No, you shouldn't. See [this](http://stackoverflow.com/a/37374041/3731501). – Estus Flask Jun 22 '16 at 17:38
  • Does the `user(dataService, { ... })` work synchronously? (i.e. does it return immediately?) – Ates Goral Jun 22 '16 at 17:43
  • @AtesGoral It works asynchronously. – Bren Jun 22 '16 at 17:44
  • @estus: In the link you provided you say "$q promises can be duck-typed by $$state property". But I did not do that explicityly. So, Chai as promised does it implicitly now ? – Bren Jun 22 '16 at 17:48
  • Yes. Without this hack you need to launch $q chain manually with `$rootScope.$apply()`. – Estus Flask Jun 22 '16 at 18:26
  • @estus: Ok I get it that hack is for triggering Angular digest cycle (which I did manually). I still don't understand why it is not working with return. In the link you say "Chai as Promised's assertions are regular Chai assertion objects, extended with a single then method derived from the input promise". This bit I do not understand, does that mean Angular q$ promises are not compliant ? It has a then function, Does Chai not wrap it ? – Bren Jun 22 '16 at 18:51
  • It returns pending $q promise that remains unsettled until $rootScope.$apply() is called, i.e. forever. Chai-as-promise chains this pending promise, and its `then` callback is never called, too. $q promises are tied to Angular digests, this allows them to be synchronous. Any other promises are asynchronous. So if this means that $q is not 'compliant', that's it. – Estus Flask Jun 22 '16 at 19:02
  • @estus: That I do get.What I am saying is I do call $rootScope.$apply() before returning the promise. Check the second block of code I shared. I manually trigger digest, `then` callback should be called. – Bren Jun 22 '16 at 19:05
  • In this case Mocha chains returned promise with `then` *after* `$rootScope.$apply()` was called. So chained `then` needs another `$rootScope.$apply()` to be executed. Again, $q promises are synchronous, and Mocha's promise returns are meant for asynchronous specs. – Estus Flask Jun 22 '16 at 19:21
  • @estus Ah, I see! Now it makes perfect sense. I see how $q promises are synchronous. Thanks for your patience. If you could post an answer with this explenation I will mark it as accepted. – Bren Jun 22 '16 at 19:27

1 Answers1

2

In the case above Mocha chains returned promise after $rootScope.$apply() was called, so chained then needs another $rootScope.$apply() to be executed. Without this the rest of promise chain is not executed and results in timeout.

Returning promises in Mocha specs is intended for asynchronous specs, this is necessary for testing non-Angular promises. $q promises are synchronous and tied to Angular digests.

As shown here, chai-as-promised can be modified to support $q promises and apply $rootScope.$apply() automatically to asserted promises:

chaiAsPromised.transferPromiseness = function (assertion, promise) {
  assertion.then = promise.then.bind(promise);

  if (!('$$state' in promise))
    return;

  inject(function ($rootScope) {
    if (!$rootScope.$$phase)
      $rootScope.$digest();
  });
};
Community
  • 1
  • 1
Estus Flask
  • 206,104
  • 70
  • 425
  • 565