Search code examples
unit-testingvue.jsvuejs2jestjsvue-test-utils

Vue-test-utils Mock fetch response from another component


I'm trying to set up unit tests using vue-test-utils and jest on some Vue components that retrieve responses from another Vue component (ApiCalls.vue) that is in charge of making calls to a remote API using fetch(). The component that makes API calls returns objects of this type :

{
  status: <fetch response status>,
  body: <fetch response body>
}

Component to be tested overview (MyComponent.vue) :

import ApiCalls from './ApiCalls.vue';

export default {
  data() {
    return {
      resultsFromAPI: null
    }
  },
  mounted() {
    this.getSomeData();
  },
  methods: {
    async getSomeData() {
      const APIResponse = await ApiCalls.data().getSomeData();
      if (APIResponse.status === 200) {
        this.resultsFromAPI = APIResponse.body;
      }
    }
  }
}

Test spec overview :

import { shallowMount } from '@vue/test-utils';
import MyComponent      from './MyComponent.vue';

const wrapper = shallowMount(MyComponent);

test('check data from API', () => {
  expect(wrapper.vm.resultsFromAPI).toBe(<stuff fromAPI>);
});

I tried to mock the function getSomeData like this :

wrapper.vm.getSomeData = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({status: 200, body: { result: 'blabla' }),
  })
);

Or

const getSomeData = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({status: 200, body: { result: 'blabla' }),
  })
);

But it doesn't works and I can't find any clue in the vue-test-utils nor jest config... Any help would be appreciated.


Solution

  • First of all, you should never call data() function on a Vue constructor. I mean, you can do it just for kicks, but you already know what it's going to return, right? That's no mystery. The "magic" is the constructor creates reactive properties on the returned Vue instance for each member of data()'s result with a value other than undefined

    Now, getting to the real problem with your mocking attempts: you seem to have misunderstood the purpose of mocking and, therefore, the purpose of unit testing. Mocking is replacing an external system (to your component) with something that always behaves as that external system is expected to behave. Because you should only test the current unit (component) and nothing else. Your test should not depend on whether or not the machine running the test has access to the API, if the API is currently down, etc...

    But do not ever mock the contents of the component being tested!

    If you do that you create the possibility for your tested component to be broken while the test still passes, as the broken method is replaced in the test with a solid mock. You want a test which runs the actual code inside your method, which fails when the component fails and which passes when the component behaves as expected.

    You should test if the component generates the expected output for all possible inputs.

    In your case, you should mock ApiCalls with something behaving as you expect ApiCalls to behave, and there are basically two things you should test:

    • ApiCalls.getSomeData is called once (no less, no more), when current component has been mounted (and that it was called with the correct params)
    • the component's .resultsFromAPI are populated with the returned data when the response status is 200 and remain null otherwise.

    Your component shouldn't care what ApiCalls is. In other words, ApiCalls should be mock-able. It could be something that actually calls the backend or a backend mock. All that matters is how your component reacts to different responses from whatever ApiCalls's methods return.

    One last thing you could test is what happens when ApiCalls is not available, if you think that's an actual possibility (or what happens if it never resolves - it stalls). These are edge cases and not normally covered, unless there's a specific request from the client the application should recover from structural bugs (it's a rare case).


    To sum up, never write a test that could pass when the actual component is broken. It's one of those things which could get you fired on the spot (it's a metaphor - replace with whatever you could lose in case of failure: clients, customers, lives, accounts, reputation, etc...), if anything significant actually depends on whether or not your component performs as expected. Because not only the test you wrote failed to catch the bug, but it actually lied about having tested the component (which it never does - it tests the component mock). So it gives a false and dangerous sense of security.


    Use methods described in this answer for mocking ApiCalls' methods.

    Note: in all fairness, I could have marked your question as duplicate of the above and of many others. This has been asked before.
    However, I chose to post this lengthy warning as an answer (in the hope you and perhaps others will find it useful) and to outline the dangers of not mocking what should be mocked and of not testing properly, as I think it's a big problem with how UTs are often times written.
    Some of the blame for this type of mistakes (and their danger) also lies with UTs being made mandatory nowadays, while too few coding companies put any effort into teaching testing principles.