4

I already looked everywhere but could not find a solution yet for my particular case.

We are using angular 1.5 and a Karma/Jasmine setup for unit tests. In the initial source code, I used ES2017 async/await in the controller. That seemed to work fine as long as I added $apply of $digest manually at the end. So for example:

async function loadData() {
  try {
    vm.isLoading = true;
    vm.data = await DataService.getData();
    $scope.$apply();
  }
  catch (ex) {
    vm.isLoading = false;
  }
}

To write an automated test for this particular function, I tried to mock DataService.getData with Jasmine's spyOn. So, I did something like this:

spyOn(DataService, 'getData').and.returnValue($q.when(fakeResult));

Adding the spy worked, but when running a test, the code seems to get struck and not resolve with fakeResult. I tried adding $digest/$apply in the tests itself but could not fix it. I also did a lot of research, but still have no clue.

Does somebody have a clue?

Edit: testing the same method with $q promises works fine, but I would really like to use async/await...

dejakob
  • 2,062
  • 14
  • 21
  • 2
    FYI, `async/await` is part of ES2017, which is to be released next year, not ES7 (which was released this year). – Felix Kling Dec 09 '16 at 05:50

3 Answers3

1

I don't know if your setup is similar, but in our environment I had to do a couple of things to get transpiled async/await statements to resolve in jasmine tests.

  • In our tests, I was trying to mock out services that would return promises. I found that returning $q.when didn't work. Instead, I had to return actual A+ standard Promises. My guess is that Angular's $q promises aren't fully complaint with the standard and wouldn't work as a substitute.

  • Note that since we use PhantomJS for our tests, I had to add polyfills to get those Promises.

  • Many times in my tests I would have to wrap some of the expect statements in a setTimeout block. Again, my assumption is that the promises need an additional "tick" to process.

tvsbrent
  • 2,350
  • 1
  • 10
  • 9
1

So the reason why there is no result is because nothing after await is executed. I had a similar issue when I was testing async/await inside react components. I found somewhere that a testing method for this is as follows:

  1. You have to use something similar to https://www.npmjs.com/package/jasmine-async-suite - this is what I used. It is for asynchronous tests where you are expecting a promise.
    • There could be also an other problem, that after the promise is resolved, the test stops, because there is nothing to wait for :). It could be very tricky.
  2. So you have to manually call the method, in your case DataService.getData(), and use the .then() method, where you can put your expect statements - because of this step, your test is waiting for resolving the promise.

Here is an example of my code (I am also using async functions in tests):

it.async('should call `mySpecialMethod`', async () => {
    const arrayToResolve = [ { data: 'data' } ];
    const SomeService = context.SomeService;

    spyOn(props, 'mySpecialMethod');
    spyOn(SomeService, 'myMethodReturningPromise');
      .and.returnValue(Promise.resolve(arrayToResolve));

    //the method `myMethodReturningPromise` is called in componentDidMount
    const wrapper = mount(<MyReactComponent {...props} />, context);
    expect(SomeService.myMethodReturningPromise).toHaveBeenCalled();

    //now I am calling the method again
    await SomeService.myMethodReturningPromise();

    //`mySpecialMethod` is calling after the `await` in my code
    expect(props.mySpecialMethod).toHaveBeenCalled();
    //after that I am changing the state
    expect(wrapper.state().arrayToResolve).toEqual(arrayToResolve);
});

I hope this helps you :)

casperle
  • 11
  • 2
0

You can use the library async-await-jasmine:

import * as angular from 'angular';
import 'angular-mocks';
import {yourModule} from "./your-module-path";
import {LoadDataService} from './load-data';
import {$it} from "async-await-jasmine";
import {IRootScopeService} from "angular";


describe('Calculator', () => {
  let $httpBackend: angular.IHttpBackendService;

  beforeEach(() => {
    angular.mock.module(calculatorModule.name);
    angular.mock.inject((_$httpBackend_) => {
      $httpBackend = _$httpBackend_;
    });
  });

  $it('should loadData', async () => {
    $httpBackend.when('GET', '/loadData').respond('{"value": 5}');
    let sum = loadData(1, 4);
    $httpBackend.flush();

    expect(await sum).toBe(10);
  });
});
Luis Vargas
  • 2,466
  • 2
  • 15
  • 32