vue-test-utilsnuxt3.jsvitest

How to mock Nuxt's useLazyFetch method with Vitest


I'm trying to mock a call of Nuxt's useLazyFetch() method and let it respond with a .json fixture in a test. The stack I'm using is as follows:

Other answers to questions like this on Stackoverflow seem to be pointing at mocking Vue methods only but not Nuxt specific ones. The component using the tested Comp.vue one wraps it in a <Suspense> tag.

The component under test looks like this:

Comp.vue

<script setup lang="ts">
  // tried either of these imports for the test, or none using global components/composables
  //import { useLazyFetch } from '#app';
  //import { useLazyFetch } from '@/node_modules/nuxt/dist/app/composables/fetch';

  const {
    pending,
    error,
    data: myData,
  } = await useLazyFetch<MyResponseType>('https://some-url');
</script>

<template>
  <div>
    <div v-if="pending || error">...</div>
    <div v-else-if="!pending && !error && myData">
      <Child
        v-for="data of myData"
        <...>
      />
  </div>
</template>

Several different ways to solve it didn't lead to a solution. The raw test basically looks like this with a few setups shown in parallel:

import { describe, test, expect, vi } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import * as jsonResponse from './jsonResponse.test.json';

describe('Comp', () => {
  const underTest = mount(Comp, {
    global: {
      mocks: {
         // 1st
         $fetch: {
          pending: false,
          error: undefined,
          myData: jsonResponse,
        },
        
        // 2nd
        useLazyFetch: new Promise((resolve) => {
          resolve(jsonResponse);
        }),

        // 3rd
        myData: jsonResponse,
        pending: false,
        error: undefined,

        // 4th
        useLazyFetch: vi.fn().mockImplementation(() =>
          Promise.resolve({
            pending: false,
            error: undefined,
            myData: jsonResponse,
          })
        ),

        // 5th
        useLazyFetch: () => {
          Promise.resolve({
            pending: false,
            error: undefined,
            myData: jsonResponse,
          });
        },
      },
    },
  });

  test('test', async () => {

    // also tried these, based on other findings
    await new Promise((resolve) => setTimeout(resolve));
    await flushPromises();

    // should find 3 times Child component with .wrapper__entry belonging to Child
    const entries = underTest.findAll('.wrapper__entry');
    await expect(entries.length).toEqual(3);
  });
});

Among tested options are:

Mock import

vi.mock('@/node_modules/nuxt/dist/app/composables/fetch');
const lazyFetch = await import('@/node_modules/nuxt/dist/app/composables/fetch');
lazyFetch.useLazyFetch = vi.fn().mockResolvedValue(jsonResponse);

vi.mock()

Using this import in Comp.vue: import { useLazyFetch } from '#app';

vi.mock('app', () => ({
    useLazyFetch: Promise.resolve({
      pending: false,
      error: undefined,
      myData: jsonResponse,
    }),
  }));

vi.spyOn()

vi.spyOn(Comp, 'useLazyFetch').mockImplementation(() => {
    return Promise.resolve({
      pending: false,
      error: undefined,
      myData: jsonResponse,
    });
  });

vi.spyOn(underTest.vm, 'useLazyFetch').mockResolvedValue({
   myData: jsonResponse,
});

vm.$options

underTest.vm.$options.useLazyFetch = () => {
    return new Promise((resolve) => {
      resolve({
        pending: false,
        error: undefined,
        myData: jsonResponse,
      });
    });
  };

The output of console.log(underTest.getCurrentComponent()); always shows

...
asyncDep: Promise {
    <rejected> ReferenceError: useLazyFetch is not defined
...

When not using useLazyFetch() but plain variables for pending, error, myData in Comp.vue, mocking like this is sufficient:

global: {
  mocks: {
    myData: jsonResponse,
    pending: false,
    error: undefined,
  }
}

Does anyone have a hint how to solve this?


Solution

  • I was successful in a similar case, where I needed to mock the defineNuxtPlugin function of a plugin, in order to test its implementation.

    Using vitest I was able to mock the function globally using:

    vi.stubGlobal('defineNuxtPlugin', () => ({ /* your mock implementation... */ }))
    

    The docs mention stubGlobal here.

    So for your case something like this should work:

    import { describe, test, expect, vi } from 'vitest';
    import { mount, flushPromises } from '@vue/test-utils';
    import * as jsonResponse from './jsonResponse.test.json';
    
    vi.stubGlobal('useLazyFetch', async () => ({
      pending: false,
      error: undefined,
      myData: jsonResponse
    }))
    
    describe('Comp', () => {
      test('test', async () => {
        const underTest = mount(Comp, {})
        const entries = underTest.findAll('.wrapper__entry');
        await expect(entries.length).toEqual(3);
      })
    })