5

I am trying to test a Vue component with Vitest but in order to do that I need to mock auth0

Below is my Navbar.test.ts file, however when I run the test I keep getting the following error Cannot read properties of undefined (reading 'mockReturnValue' as useAuth0 seems to be undefined even though I am imported it at the top of the page. Maybe I'm just not understanding the mocking side of it very well but any help would be appreciated.

import { vi } from 'vitest'
import { ref } from "vue";
import { shallowMount } from '@vue/test-utils'
import { useAuth0 } from '@auth0/auth0-vue';
import NavBar from "@/components/NavBar.vue";

const user = {
    email: "user@test.com",
    email_verified: true,
    sub: ""
};

vi.mock("@auth0/auth0-vue");

const mockedUseAuth0 = vi.mocked(useAuth0, true);

describe("NavBar.vue", () => {
    beforeEach(() => {
        mockedUseAuth0.mockReturnValue({
            isAuthenticated: ref(true),
            user: ref(user),
            logout: vi.fn(),
            loginWithRedirect: vi.fn(),
            ...
            isLoading: ref(false),
        });
    });

    it("mounts", () => {
        const wrapper = shallowMount(NavBar, {
            props: { },
        });

        expect(wrapper).toBeTruthy();
    });

    afterEach(() => vi.clearAllMocks());
});
Pattle
  • 5,983
  • 8
  • 33
  • 56

1 Answers1

8

Mocking auth0.useAuth0

To access the mocked module, import the whole module with a wildcard import, and avoid named imports. Also, vi.mocked() is just a helper for TypeScript support. It doesn't mock anything.

So instead of this:

import { useAuth0 } from '@auth0/auth0-vue' ❌

vi.mock('@auth0/auth0-vue')
const mockedUseAuth0 = vi.mocked(useAuth0, true) ❌

...do this:

import * as auth0 from '@auth0/auth0-vue' ✅

vi.mock('@auth0/auth0-vue')

Then, mock useAuth0 by attaching a property directly to the mocked auth0 import:

describe('NavBar.vue', () => {
  it('does something', async () => {
                      
    (auth0 as any).useAuth0 = vi.fn().mockReturnValue({
      // relevant fields for test
    });
  })
})

The value of useAuth0 should be a mock function (vi.fn()) that returns the fields relevant to the test. Assuming the component under test is NavBar.vue from Auth0's sample app, one could write a test to verify the login button calls loginWithRedirect. That button is only available when isAuthenticated and isLoading are false. Note the fields do not need to be Vue refs.

So such a test could look like this:

describe('NavBar.vue', () => {
  it('login button invokes useAuth.loginWithRedirect', async () => {
    const loginWithRedirect = vi.fn();
    (auth0 as any).useAuth0 = vi.fn().mockReturnValue({
      isAuthenticated: false,
      isLoading: false,
      loginWithRedirect,
    });

    const wrapper = shallowMount(NavBar);
    await wrapper.find('button#qsLoginBtn').trigger('click');

    expect(auth0.useAuth0).toHaveBeenCalled();
    expect(loginWithRedirect).toHaveBeenCalled();
  })
})

Mocking useAuth().user

One could also write a test to verify the authenticated user's details are shown in the component. Specifically, the user's name is shown in .user-info, and the user's picture is shown in .user-info img when isAuthenticated is true. The mocked useAuth0 should thus return isAuthenticated and user as follows:

describe('NavBar', () => {
  it('authenticated user details are shown', async () => {
    const user = {
      name: 'john@gmail.com',
      picture: 'https://s.gravatar.com/avatar/1f9d9a9efc2f523b2f09629444632b5c?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fjo.png',
    };

    (auth0 as any).useAuth0 = vi.fn().mockReturnValue({
      isAuthenticated: true,
      user,
    });

    const wrapper = shallowMount(NavBar);

    expect(wrapper.find('.user-info').text()).toBe(user.name);
    expect(wrapper.find('.user-info img').attributes('src')).toBe(user.picture);
  })
})

demo

tony19
  • 125,647
  • 18
  • 229
  • 307