2

I have this service:

    angular.module('domeeApp')
        .factory('streamWidget', streamWidgetFactory);

    function streamWidgetFactory($q) {
        return {
            loadContent: function() { 
                return $q(function(resolve, reject) {
                    resolve('test');
                }) 
            }
        }
    }

I'm testing it with karma/mocha/chai:

describe('streamWidget', function() {
    beforeEach(module('domeeApp'));
    var streamWidget;
    var $timeout;

    beforeEach(inject(function(_$timeout_, _streamWidget_) {
        streamWidget = _streamWidget_;
        $timeout = _$timeout_;
    }));


    it('should load new content', function(done) {        
        streamWidget.loadContent()
        .then(function(res) {
            expect(res).to.equal('test');
            done();
        })
        .catch(function(){})
        $timeout.flush();
    });    
});

Since $q promises doesn't work well with mocha i'm following this answer, which says to add $timeout.flush() to force the .then method of the promise to be executed.

The problem is, after calling .flush(), all my app wakes up and i start to get this errors from angular-mocks:

Error: Unexpected request: GET /partials/page/view/index.

I know about $httpBackend, but it would be insane to mock ALL the requests my app is making on startup.

Is there a way to make $q promises work with mocha without calling $timeout.flush() or $rootScope.$apply()?

Community
  • 1
  • 1
pietrovismara
  • 6,102
  • 5
  • 33
  • 45
  • The subject of the question is Chai, not Mocha. Possible duplicate of http://stackoverflow.com/a/37374041/3731501 – Estus Flask Jul 29 '16 at 16:17
  • The subject of the question is the $q service and how i can force $q promises to be resolved without using apply/flush. Chai as nothing to do with it, since i'm not using chai-as-promised. At least, this is how i see the situation, if you don't agree, try to explain it better. – pietrovismara Jul 29 '16 at 16:25
  • It is Chai that does assertions, not Mocha. Well, you may consider using chai-as-promised then, it is the way how $q promise assertions can be done without calling `$apply()` or `$digest()` after `then`. – Estus Flask Jul 29 '16 at 16:30
  • I tried with chai-as-promised, but with the same result. If you can show me a working solution with it, i'll accept your answer. – pietrovismara Jul 29 '16 at 16:33
  • Yes, `chai-as-promised` requires some extra setup, as the link above suggests. Fortunately it has got `transferPromiseness` hook to provide the desired behaviour for asserted promise (i.e. run a digest to execute the whole promise chain). – Estus Flask Jul 29 '16 at 16:42

2 Answers2

1

As shown here, chai-as-promised can be used to assert $q promises. With this setup

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

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

  inject(function ($rootScope) {
    if (!$rootScope.$$phase)
      $rootScope.$digest();
  });
};

digest cycles will be triggered automatically on promise assertions, executing the whole promise chain.

In this case the spec

it('...', () => {
  ...
  expect(...).to.eventually...;
  expect(...).to.eventually...;
  $rootScope.$digest();
});

can omit $digest() call and become

it('...', () => {
  ...
  expect(...).to.eventually...;
  expect(...).to.eventually...;
});

Notice that $q promises are synchronous, they shouldn't be returned from Mocha spec or call done callback.

Community
  • 1
  • 1
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I tried your solution, but the result is the same. When i call `$rootScope.$digest()`, be it in `chaiAsPromised.transferPromiseness` or wherever else, i start to get the same errors about unexpected requests. In fact, what i'm asking from beginning is how to do this test WITHOUT using $digest/$apply/$flush. At this point i guess there is no solution. – pietrovismara Aug 01 '16 at 07:49
  • 1
    It seems that you mix the things up. These all are separate concerns. `$digest()` isn't a panacea. You NEED to trigger `$digest()` to execute $q promises (this is what the answer covers). You NEED to call `flush()` to execute $timeout timeouts. You NEED to mock $httpBackend requests in unit tests. There's no need to mock them all - only the requests that are made by tested unit. This is how Angular testing works. – Estus Flask Aug 01 '16 at 09:34
0

Here's an alternative strategy that we use because we never actually need $httpBackend, but it sometimes (randomly) fails making requests for templates used by directives (even though those templates are available in $templateCache):

beforeEach(function() {
  module('app', function($provide) {
    // This is using jasmine, but the idea is the same with mocha.
    // Basically just replace $httpBackend with a function that does nothing.
    $provide.constant('$httpBackend', jasmine.createSpy('$httpBackend'));
  });
});

Of course, if you actually use $httpBackend in other cases, then this won't work, as you'll need it to mock response objects.

tandrewnichols
  • 3,456
  • 1
  • 28
  • 33