Skip to content
GitHub Twitter

Mock Clerk Authentication State with Vitest

Background on Mocking

The goal of unit testing is to test code as a modular unit/component/function, and not be concerned with the behaviour of external dependencies such as external libraries, databases or other parts of your own app.

Mocking is a solution to ensure the full isolation of the unit to be tested. The idea is to replace the inner workings of external dependencies with expected repeatable outputs, so that you have a clean slate upon which to run your unit tests. Authentication is therefore a prime candidate for this approach. We use Clerk here as the

Set up Clerk

Firstly install and setup Clerk as per the official Next.js docs.

Set up Vitest

Install using your favourite package manager.

pnpm add -D vitest @vitejs/plugin-react

Then set up a vitest.config.ts file in the root of your project.

/// <reference types="vitest" />

import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: "jsdom",
    mockReset: true,
  },
});

Create Component you wish to Unit test

// src/components/ClerkComponent.tsx
import { SignOutButton, SignInButton, useUser } from "@clerk/nextjs";

const ClerkComponent = () => {
  const { user, isSignedIn, isLoaded } = useUser();

  return (
    <div>
      {isSignedIn && (
        <SignOutButton>
          <button>Sign out</button>
        </SignOutButton>
      )}
      {!isSignedIn && (
        <SignInButton>
          <button>Sign in</button>
        </SignInButton>
      )}
    </div>
  );
};

export default ClerkComponent;

Notice that we have an external dependency on the @clerk/nextjs library. We will need to mock this dependency in order to test our component in isolation.

Setting up Vitest Mock

We will use the vi.mock function to mock this external dependency. The first argument to vi.mock is the name of the module you wish to mock, and the second argument is a function that returns the mocked module. Note here that we only need to mock the methods that we are importing from the @clerk/nextjs library - namely SignInButton, SignOutButton and useUser.

Also notice that for this setup, the isSignedIn property is set to false, meaning that the SignOutButton will not be rendered, and the SignInButton with the inner text "Sign in" will be rendered.

import { vi } from "vitest";

vi.mock("@clerk/nextjs", () => {
  // Create an mockedFunctions object to match the functions we are importing from the @nextjs/clerk package in the ClerkComponent component.
  const mockedFunctions = {
    SignInButton: ({ children }: SignInOutButtonProps) => <div>{children}</div>,
    SignOutButton: ({ children }: SignInOutButtonProps) => (
      <div>{children}</div>
    ),
    useUser: () => ({
      isSignedIn: true,
      user: {
        id: "user_2NNEqL2nrIRdJ194ndJqAHwEfxC",
        fullName: "Charles Harris",
      },
    }),
  };

  return mockedFunctions;
});

Writing the unit test

Now we can write our unit test. Even if you have only useed Jest thus far, the syntax should be familiar to you:

// test/ClerkComponent.test.tsx
import { it, render, screen } from "vitest";
import { ClerkComponent } from "..src/components/ClerkComponent";

it("Test Clerk signin", () => {
  render(<ClerkComponent />);
  screen.getByText("Sign in");
});

Assuming the mock had been set up with iSignedIn set to true, the test would fail, as the SignOutButton would be rendered instead of the SignInButton, and we would expect the text to be "Sign out".

Conclusion

With this setup, we can easily integrate unit test authed components in a predictable way, without having to make any network requests to Clerk. The code for this post can be found here.