Why is my Jest mockReturnValue function not invoking? (Using Vue3 and Vue-Test-Utils-Next)

6.8k views Asked by At

I am using Vue's composition API and importing a module into a single-file component. I assign the return value of the module to a variable within the setup method and then return the value so the template can consume the variable. The component renders one ItemComponent for each item recieved from the imported module.

The actual application is working fine and renders the correct amount of items. My test, on the other hand, is failing, and it appears the mockReturnValue function is not getting invoked.

Single-File Component Code

<template>
  <ul class="ItemList">
    <ItemComponent
      v-for="item in items"
      :key="item.id"
      :item="item" />
  </ul>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import ItemComponent from '@/components/ItemComponent.vue';

import module from '@/module';

export default defineComponent({
  components: {
   ItemComponent,
  },
  setup() {
    const { items } = module.property // note destructuring here
    console.log(items);
    return { items };
  },
});
</script>

mock-module.ts

export const property = {
  items: jest.fn(),
};

(mock) index.ts

import {module} from './modules/mock-module';

export default {module}

Test file

  import module from '@/modules/__mocks__/index.ts';
  jest.mock('../../../src/modules/index.ts');

  it('should render an ItemComponent for each item', () => {
    module.property.items.mockReturnValue([{}, {}, {}]); // Doesn't seem to get invoked?
    const wrapper = mount(MyComponent, { shallow: true });
    expect(wrapper.findAllComponents('item-component-stub').length)
      .toBe(3);
  });

Result

The test fails and finds 0 items, and when I console.log(items) from within the single-file component, I get the following:

 [Function: mockConstructor] {
        _isMockFunction: true,
        getMockImplementation: [Function (anonymous)],
        mock: [Getter/Setter],
        mockClear: [Function (anonymous)],
        mockReset: [Function (anonymous)],
        mockRestore: [Function (anonymous)],
        mockReturnValueOnce: [Function (anonymous)],
        mockResolvedValueOnce: [Function (anonymous)],
        mockRejectedValueOnce: [Function (anonymous)],
        mockReturnValue: [Function (anonymous)],
        mockResolvedValue: [Function (anonymous)],
        mockRejectedValue: [Function (anonymous)],
        mockImplementationOnce: [Function (anonymous)],
        mockImplementation: [Function (anonymous)],
        mockReturnThis: [Function (anonymous)],
        mockName: [Function (anonymous)],
        getMockName: [Function (anonymous)]
      }

This indicates (to me) that the test is using the mock file, but for some reason, is not invoking the .mockReturnValue() call. If I simply change the mock dependency from jest.fn() to some actual value, the console.log statement returns that value. Of course, I don't want to do that, as I need to be able to set different return values in different tests.

What I Expect I would expect when I call module.property.items.mockReturnValue([{}, {}, {}]) that the console.log would print an array with three empty objects, and the test would pass with 3/3 ItemComponents rendered.

1

There are 1 answers

0
Estus Flask On

items: jest.fn() makes items a spy. Console output is the one that should be expected, it shows that it's a function. mockReturnValue has no chance to be not invoked if the test continues. Its result can be seen as:

module.property.items.mockReturnValue([{}, {}, {}])
expect(module.property.items()).toEqual([{}, {}, {}])

Since items is supposed to be an array and not a function, it was mocked the wrong way. This is commonly done by mocking properties directly with no jest.mock performed on this module:

module.property = [{}, {}, {}]

It should be additionally guaranteed that changed value won't affect other tests, e.g. by doing this in jest.isolateModules to reimport a hierarchy of affected modules.

This can be done with Jest spies as well but requires to keep a reference to getter function to access it easily:

export const property = {
  items: null,
};

export const mockPropertyItems = jest.spyOn(property, 'items', 'get');

And used as:

module.mockPropertyItems.mockReturnValue([{}, {}, {}])
expect(module.property.items).toEqual([{}, {}, {}])

Also importing directly from __mocks__ is wrong, mocked module should be imported from its regular location.