vue.jsunit-testingnuxt.jstddvitest

How to mock composable with vitest?


I'm learning TDD in simple Nuxt.js project. This project is frontend of Laravel API. And what this project does is to implement all Laravel Fortify options. So I'm using external package called nuxt-auth-sanctum. Because Fortify, uses Sanctum.

My goal is to test this middleware, which checks if the user email is verified :

import { defineNuxtRouteMiddleware, navigateTo } from 'nuxt/app'
import type { User } from '~/models/user'
import { useSanctumAuth } from '#imports' // This is here because VS Code has problem recognising Nuxt.js auto-imports

export default defineNuxtRouteMiddleware(async (to, from) => {
  const { user } = useSanctumAuth<User>()

  // List of routes to exclude from this middleware
  const excludedRoutes = [
    '/auth/login',
  ]

  // Check if the current route is in the excluded routes
  if (excludedRoutes.includes(to.path)) {
    return
  }

  // Check if the user's email is verified
  if (user.value && !user.value.email_verified_at) {
    return navigateTo('/auth/verify-email')
  }
})

Here, useSanctumAuth is composable which comes from nuxt-auth-sanctum package. It's role is to return user.

And I want to mock that composable in my test:

import { describe, it, expect, vi, beforeEach } from 'vitest'
import checkEmailVerified from '@/middleware/check-email-verified.global'
import type { RouteLocationNormalized } from 'vue-router'
import { navigateTo } from 'nuxt/app'

vi.mock('nuxt/app', () => ({
  navigateTo: vi.fn(),
  defineNuxtRouteMiddleware: vi.fn((middleware) => middleware),
}))

vi.mock('#imports', async () => ({
  useSanctumAuth: vi.fn(() => ({
    user: {
      value: {
        email_verified_at: null,
      },
    },
  })),
}))

describe('checkEmailVerified middleware', () => {
    beforeEach(() => {
        vi.clearAllMocks()
    })

    it('should allow access to excluded routes without redirection', async () => {
        const to: RouteLocationNormalized = {
          path: '/auth/login',
        } as RouteLocationNormalized

        const from: RouteLocationNormalized = {
          path: '/',
        } as RouteLocationNormalized

        await checkEmailVerified(to, from) // Error: [vitest] No "useNuxtApp" export is defined on the "nuxt/app" mock. Did you forget to return it from "vi.mock"?

        expect(navigateTo).not.toHaveBeenCalled()
   })
})

When I run the test, I'm getting this error:

[vitest] No "useNuxtApp" export is defined on the "nuxt/app" mock. Did you forget to return it from "vi.mock"?

on that line:

await checkEmailVerified(to, from)

useNuxtApp comes from useSanctumAuth composable, which I want to mock. As I'm aware, mocking composable (in my case) means, that real code is not called but mocked one instead. Then why I'm getting that error? It should'n go inside useSanctumAuth at all. It is mocked to return user.

What am I doing wrong?


Solution

  • With the help of @Kapcash, now it works. I have to use what Nuxt offer first and then Vitest. Here is what I've changed: instead of

    vi.mock('#imports', async () => ({...
    

    now I have:

    import { mockNuxtImport } from '@nuxt/test-utils/runtime'
    ....
    
    const mockUser = ref<User | null>(null)
    mockNuxtImport('useSanctumAuth', () => {
      return () => ({
        user: mockUser,
      })
    })
    
    beforeEach(() => {
            vi.clearAllMocks()
            mockUser.value = null
        })