knee-cola / jest-mock-axios

Axios mock for Jest
252 stars 42 forks source link

Using with vue component tests #80

Open nirajfu3e opened 2 years ago

nirajfu3e commented 2 years ago

I am trying to use the package with a axion request on a vue component following the basic example but maybe I am not understanding the basics here, I can't get a then spy to have been called.

<!-- Component.vue -->
<input
  class="search-input form-control"
  id="field-text"
  type="text"
/>

<div class="search-results" v-if="searchResults">
    <div
        v-for="searchResult in searchResults"
        :key="searchResult.id"
        class="search-result"
    >
        {{ searchResult.country }}
    </div>
    <div
        v-if="searchResults.length === 0"
        class="no-results"
    >
        No result found
    </div>
</div>
<script>
onInput: _.debounce(function(event) {
    api.search(this.term).then(
        ({data}) => this.results = data
    )
}, 300)
</script>

Then the test which uses vue-testing-library is as follows

// Component.spec.js
it('renders properly', async (done) => {
    let catchFn = jest.fn(),
        thenFn = jest.fn();

    // using the component, which should make a server response
    let term = 'London';

    render(Component, {
            localVue,
            store: store,
        })
    }

    const input = screen.getByRole('textbox')
    await userEvent.type(input, term)
    await userEvent.click(document.body)

    // Set timeout is required since there is a debounce of 300ms on input field
    setTimeout((async () => {
        done()

        // since `post` method is a spy, we can check if the server request was correct
        // a) the correct method was used (post)
        // b) went to the correct web service URL ('/search')
        // c) if the payload was correct ('{term: term}')
        expect(mockAxios.post).toHaveBeenCalledWith('/search', {term: term });

        // simulating a server response
        let responseObj = {
            "data": [
                {
                    "id": 1,
                    "country": "Foo",
                },
                {
                    "id": 2,
                    "country": "Bar",
                },
            ]
        };
        mockAxios.mockResponse(responseObj);

        // checking the `then` spy has been called and if the
        expect(thenFn).toHaveBeenCalled();

        // catch should not have been called
        expect(catchFn).not.toHaveBeenCalled();

        // debug the output
        screen.debug();
    }), 400)
});

In the test the first assertion (expect(mockAxios.post).toHaveBeenCalledWith('/search', {term: term });) works as expected. It mocks the response as well but it fails on the expect(thenFn).toHaveBeenCalled(); showing there were 0 calls for thenFn.

I was also expecting the results on the this.results = data to be populated with the mocked response data and the template updated, just like when you get a response from an actual api endpoint would but it looks like that never happens. Am I missing something here?

kingjan1999 commented 2 years ago

Hi,

it looks like your example is incomplete. You're initializing thenFn with jest.fn() but don't reference it somewhere else. Thus, it never get's called.

nirajfu3e commented 2 years ago

@kingjan1999 Thank you for the very quick response and you are indeed correct. I forgot to ask where would I call it as the example in the readme, it is used as follows:

UppercaseProxy(clientMessage)
  .then(thenFn)
  .catch(catchFn);

I tried doing this but it didn't work as expected since the render function doesn't return a promise.

  render(Component, {
            localVue,
            store: store,
        })
    }).then(thenFn)
  .catch(catchFn);
kingjan1999 commented 2 years ago

@nirajfu3e In the readme example, UppercaseProxy returns a promise. Therefore, it is easy to chain another then or catch on the returned promise. In your example, the promise used in onInput can't be accessed from the test. Consequently, you can't just attach a spy function to verify the actual then callback is executed. You need to verify this based on the actions done there, e.g on what is set to this.results or actually rendered by Vue.

nirajfu3e commented 2 years ago

@kingjan1999 That makes perfect sense and that is how I was planning to test it - by checking what was actually rendered by Vue. For some reason the mocked data set with responseObj doesn't seem to be applying on this.results and the vue template doesn't get updated to display the results.

Am I correct in assuming that when we simulate the server response, on Component.vue it should have used the mockedResponse created with mockAxios.mockResponse(responseObj) and update the template rendered by Vue just like it would outside of test environment?

Thanks.

kingjan1999 commented 2 years ago

@nirajfu3e That's exactly what should happen, yes. Can you check whether the then callback is called at all (e.g. with some logging)? Or provide a minimal working example (repo), so that I can look further into this?

nirajfu3e commented 2 years ago

@kingjan1999 I did console.log and when I mock the response object I can see the output of the data but strangely, I can't seem to use array destructing ({data}) => this.searchResults = data (returns undefined). If I simply do (data) => this.searchResults = data, then it shows the data when the response is mocked but still doesn't seem to update the rendered value.

I tried creating a CodeSandbox but for some reason the setup is not working on it but I have got the tests with an API mocked there.