axios-mock-adapter icon indicating copy to clipboard operation
axios-mock-adapter copied to clipboard

Would love to have a workaround for this timeout

Open martynchamberlin opened this issue 8 years ago • 6 comments

In Angular testing for a service that implements $http, we're using to doing this:

$httpBackend.flush();

What this does is it takes any outstanding requests and processes them so that you can then begin the assertion process. See its docs for more.

Anyway, I'm bringing this up as an example because it would be really nice to be able to have this for the axios mocker. Instead of this flush() method or something similar, we're forced to create a timeout with a zero millisecond duration and then nest all of our expectations within that timeout. For an example of this, see here:

import { expect } from 'chai';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { fetchPost } from './lib'

describe('Lib', () => {
  it('Should return data from response', (done) => {
    let mockAdapter = new MockAdapter(axios);

     mockAdapter.onGet('http://awesome.com/posts').reply(200, {
       data: {
         posts: ['Intro to git']
       }
     });

     let response = fetchPost();

     setTimeout(() => {
        expect(response.posts[0]).to.be.equal('Intro to git');
        done();
     }, 0)
  });
});

I've confirmed in my own local code that without the timeout, you indeed cannot expect response.posts[0] to be resolved, but you can inside the timeout. I don't like this syntax though because it forces all of the code to be nested another level, it's not as linear, and it assumes some things about how your axios promiser is implemented (e.g. it breaks on an otherwise perfectly functioning custom wrapper that I wrote for fun).

Would it be possible to have some sort of $httpBackend.flush(); equivalent for this axios mocker?

Thanks!

martynchamberlin avatar Dec 29 '16 05:12 martynchamberlin

I'm not very familiar with Angular, so I don't have an answer straight away. But are you sure your problem is related to the piece of code you've pasted? fetchPosts returns a promise, so in the test you need to return a promise with the assertion instead of using setTimeout.

ctimmerm avatar Dec 29 '16 08:12 ctimmerm

If I understand correctly, you're saying that the code above should be reformatted to the following:

describe('Lib', () => {
  it('Should return data from response', (done) => {
    let mockAdapter = new MockAdapter(axios);

     mockAdapter.onGet('http://awesome.com/posts').reply(200, {
       data: {
         posts: ['Intro to git']
       }
     });

     return fetchPost().then((response) => { 
        expect(response.posts[0]).to.be.equal('Intro to git');
        done();
     });

  });
});

Correct?

Assuming this is the case, that would work in situations where the function that generates the HTTP request returns a promise, but the function might not do that. Imagine if this were how fetchPost were defined:

import http from 'axios';
import eventHub from './eventhub.js';

export function fetchPost() {
 http
    .get('http://awesome.com/posts')
    .then((response) => {
        eventHub.$emit('posts', response.data);
    });
}

In other words, instead of using promises to pass the data around, we're using a simple pub/sub model. In this situation we're required to use a timeout. Would love for that to not have to be the case.

martynchamberlin avatar Dec 29 '16 15:12 martynchamberlin

Running into this same issue. Basically the exact same situation as @martynchamberlin describes in the post directly above me.

Actually, for the time being I am grateful he introduced me to the setTimeout(0) workaround. But I would definitely +1 this request for a way to not need that workaround.

jasongaare avatar Feb 01 '17 18:02 jasongaare

Same here, using this for Vue.js unit testing, but it won't resolve any of the promises I did inside the components without using a setTimeout trick.

@jasongaare @martynchamberlin

function itv (description, callback) {
  it(description, function(done) {
    callback((capture) => {
      setTimeout(() => {
        capture()
        done()
      }, 0)
    })
  })
}

could serve to reduce boilerplate a bit:

itv('should render correct contents', (processRequests) => {
    mock.onGet('/api/foo').reply(200, apidata.foo)
    mock.onGet('/api/bar').reply(200, apidata.bar)

    processRequests(() => {
      expect(1+1).to.be(2):
    })
  })

Busata avatar Feb 06 '17 09:02 Busata

@Busata Wow that code bends my mind a bit! But you're right, it would definitely cut down on verbosity a bit. Thanks for sharing.

martynchamberlin avatar Feb 06 '17 16:02 martynchamberlin

The best way I found to modify the async action creator to return the http.get promise and then make the test an async function and set the action dispatcher with an await call. You can then make assertions after the action dispatch.

//async action creator using redux-thunk
updateContacts: () => {
      return http.get("/api/chat").then((response) => {
        dispatch(chatActions.getContacts());
      }).catch(reason => {
      });
    };
  }

//action test
it("should update the user contacts", async () => {
    chatActions.getContacts = jest.fn();

    mock
      .onGet("/api/chat").replyOnce(200);

    await ChatStore.dispatch(actions.updateContacts());

    expect(actions.getContacts).toHaveBeenCalled();
  });

danielcardoso5 avatar Nov 06 '18 15:11 danielcardoso5