0

I am testing my Angular app using ngDescribe. I don't think ngDescribe should be too much of a problem here, as it's just managing dependency injection for me. I first began to attempt my test the way the ngDescribe docs say, in the code below I have changed it to a more direct approach just to see if I could get any changes. I am calling a method that in turn calls $http.post('urlname', data); While debugging I can clearly see that my method gets all the way to where it calls post() but then it never continues. Because of this, my test always times out.

Hopefully I've just got something simple that's wrong! Here is my code. The test that fails is "Should work", all the others pass as expected.

Please also note that this is being processed by babel, both the test and the service, before being tested.

Here is the service, it works perfectly when being used. It has a few other variables involved that I have removed, but I assure you those variables are working correctly. While debugging for the tests, I can see that the await is hit, but it never continues past that, or returns. When used in the app, it returns exactly as expected. I THINK this has something to do with ngmock not returning as it should.

async function apiCall (endPoint, data) {
    if (!endPoint) {
        return false;
    }

    try {
        return data ? await $http.post(`${endPoint}`, data) : await $http.get(`${endPoint}`);
    } catch (error) {
        return false;
    }
}

Here are the tests:

ngDescribe({
    name: 'Api Service, apiCall',
    modules: 'api',
    inject: ['apiService', '$httpBackend'],
    tests (deps) {
      let svc;
      beforeEach(() => {
        svc = deps.apiService;
      });
      it('is a function', () => {
        expect(angular.isFunction(svc.apiCall)).toBe(true);
      });
      it('returns a promise', () => {
        const apiCall = svc.apiCall();
        expect(angular.isFunction(apiCall.then)).toBe(true);
      });
      it('requires an endpoint', async () => {
        const apiCall = await svc.apiCall();
        expect(apiCall).toBe(false);
      });
      it('should work', (done) => {
        deps.http.expectPOST('fakeForShouldWork').respond({ success: true });
        const apiCall = svc.apiCall('fakeForShouldWork', {});
        apiCall.then(() => done()).catch(() => done());
        deps.http.flush();
      });
    },
  });

The method being called, apiCall, is simply a promise that is resolved by $http.post().then(); It will also resolve false if an error is thrown.

Since deps.http.expectPOST does not fail, I can tell that the outgoing request is sent. I validated this by changing it to expectGET and then I received an error about it being a POST.

I have tried moving the flush() method to all different parts of the test method, but it seems to make no difference.

Any thoughts? Thanks so much for your help!

Justin
  • 2,265
  • 4
  • 15
  • 21
  • 1
    What's `apiCall`? If this is a pending promise, the spec will obviously time out. And in the spec `svc` is undefined and doesn't have `svc.apiCall` at all! The question doesn't contain the code that is being tested. The fact that exotic ngDescribe wrapper is used instead of plain Mocha/Jasmine doesn't make the things clearer. – Estus Flask Jul 18 '16 at 21:27
  • @estus I updated my response to clarify a bit. apiCall is a promise that is resolved when $http.post().then() returns. It is working fine outside of the testing environment. ngDescribe is really just managing my dependencies for me, so that I don't have to inject all the angular dependencies manually. – Justin Jul 18 '16 at 21:32
  • 1
    Please, resolve the issue with undefined `svc` and specify the code that you're testing instead of describing it. The question cannot get any answer but 'there's something wrong' in its current state, and you probably already know that. – Estus Flask Jul 18 '16 at 21:43
  • @estus I added the function being called and I fixed the part of the tests that I left out, it should make a little more sense now. – Justin Jul 19 '16 at 01:06
  • 1
    The problem is most likely caused by mixed $q ( $http and all Angular stuff) and native (`async...await`) promises, it is a race condition. Native promise chains run on the next tick. And $q promise chains are triggered by digests and run immediately. `apiCall` is native promise. And `flush()` triggers a digest when `$http.post` chain wasn't formed yet. When it is formed, it requires an another digest to execute the rest of the chain. I guess something like `setTimeout(() => deps.http.flush(), 0)` may work, add delay to taste. – Estus Flask Jul 19 '16 at 02:10
  • 1
    The main benefit of $q promises is that they are synchronous and can be synchronously tested, mixing them with native promises destroys the benefit. Native promises (and `async...await` in particular) aren't too good for Angular 1 because they may cause complex issues in production and require very good understanding of what's going on under the hood. – Estus Flask Jul 19 '16 at 02:17
  • It looks like you were correct, adding setTimeout to deps.http.flush solved my issue. – Justin Jul 19 '16 at 12:27

0 Answers0