1

I'm trying to unit testing my Angular service using async/await keyword in the corresponding (Jasmine) unit tests below. The test for the native Promise works just fine, but I'm pretty much stuck in making the Angular $q counterpart work.

  • Angular: 1.6.5
  • Jasmine: 2.7.0
  • (Headless) Chrome on MacOS: 60.x

angular
  .module('asyncAwaitTest', [])
  .factory('xService', xServiceFactory);

function xServiceFactory(
  $q,
  $timeout
) {
  return {
    getXAfter1Sec() {
      return new Promise(resolve => setTimeout(() => resolve(43), 1000));
    },
    getXAfter1SecWithAngular$Q() {
      const deferred = $q.defer();

      $timeout(() => deferred.resolve(43), 1000);

      return deferred.promise;
    }
  };
}

jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;

describe('asyncAwaitTest: x service', () => {
  let $timeout;
  let xService;

  beforeEach(() => {
    module('asyncAwaitTest');

    inject(
      (
        _$timeout_,
        _xService_
      ) => {
        $timeout = _$timeout_;
        xService = _xService_;
      }
    );
  });

  it('should work', async (done) => {
    const x = await xService.getXAfter1Sec();

    expect(x).toEqual(43);

    done();
  });

  it('should work, too. but y not?!!', async (done) => {
    const xPromise = xService.getXAfter1SecWithAngular$Q();

    $timeout.flush();

    const x = await xPromise;

    expect(x).toEqual(43);

    done();
  });
});

Fiddle provided here: https://jsfiddle.net/glenn/gaoh6bvc/

I've tried Google but it doesn't give me a significant good lead

Glenn Mohammad
  • 3,871
  • 4
  • 36
  • 45
  • `async/await` don't know about `$q`. They work with native promises. Check this out: https://stackoverflow.com/questions/35629246/typescript-async-await-and-angular-q-service – Frank Modica Jul 30 '17 at 18:34
  • I see that. But is there any workaround? Putting `.run(function runBlock($window, $q) { $window.Promise = $q; });` only making it worse - it fails both tests :/ – Glenn Mohammad Jul 30 '17 at 19:22

2 Answers2

3

You could create a helper for your test that converts the promise from $q to a native promise. Check it out here.

it('should work, too. but y not?!!', async (done) => {
  const xPromise = toNativePromise(xService.getXAfter1SecWithAngular$Q());

  $timeout.flush();

  const x = await xPromise;

  expect(x).toEqual(43);

  done();
});

function toNativePromise(promise) {
  return new Promise((resolve, reject) => {
    promise.then(val => {
      resolve(val);
    }, err => {
      reject(err);
    });
  });
}
Frank Modica
  • 10,238
  • 3
  • 23
  • 39
0

async functions are based on native promises, while AngularJS uses $q promises. await is a syntactic sugar for chaining a promise with then. $q promise chains are executed on digest in tests.

This cannot be fixed with

await xPromise;
$rootScope.$digest();

because $rootScope.$digest() isn't evaluated until $rootScope.$digest() is executed. This results in pending promise.

AngularJS shouldn't be tested with async..await in the first place. Angular was designed to be tested synchronously.

It is

it('...', () => {
  ...
  xPromise.then(x => {
    expect(x).toEqual(43);
  });
  $rootScope.$digest();
});

Or promises can be flattened with jasmine-promise-matchers:

it('...', () => {
  ...
  expect(xPromise).toBeResolvedWith(43);
  $rootScope.$digest();
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565