Trying to do testing on NextJS with convex and clerk
I'm trying to test my application frontend with Vitest and React testing library with convex. I'm struggling to do it properly.
5 Replies
Thanks for posting in <#1088161997662724167>.
Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets.
- Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.)
- Use search.convex.dev to search Docs, Stack, and Discord all at once.
- Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI.
- Avoid tagging staff unless specifically instructed.
Thank you!
I also have my setup file:
// tests/setup.ts
import '@testing-library/jest-dom';
import { vi, afterEach } from 'vitest';
import React from 'react';
/* ---------- window & DOM shims ---------- /
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(), // deprecated but some libs still call it
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));
global.ResizeObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));
/ ---------- fetch / env ---------- /
global.fetch = vi.fn();
process.env.NEXT_PUBLIC_CONVEX_URL = 'https://test.convex.cloud';
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY =
'pk_test_Y2xlcmsuaW5jbHVkZWQua2F0eWRpZC05Mi5sY2wuZGV2JA';
/ ---------- Router & Next shims ---------- /
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: vi.fn(),
replace: vi.fn(),
back: vi.fn(),
forward: vi.fn(),
refresh: vi.fn(),
prefetch: vi.fn(),
}),
usePathname: () => '/',
useSearchParams: () => new URLSearchParams(),
}));
/ ---------- Clean‑up after each test ---------- */
afterEach(() => {
vi.clearAllMocks();
vi.resetModules();
});
// Console suppression for cleaner test output (optional)
// Uncomment if you want to suppress console.log in tests
// global.console = {
// ...console,
// log: vi.fn(),
// debug: vi.fn(),
// info: vi.fn(),
// warn: vi.fn(),
// error: vi.fn(),
// };
above is my setup.ts file
I also have test providers file
An example of what I am trying to do is here:
import React from "react";
import { describe, it, expect } from "vitest";
import { screen } from "@testing-library/react";
import { renderWithProviders, createMockConvexClientWithBehavior } from "./helpers/test-providers";
import Home from "../app/page";
import { getMockRecipe } from "./helpers/mock-factories";
// Mock recipes to simulate feed data
const mockRecipes = [
getMockRecipe({ title: "First Recipe" }),
getMockRecipe({ title: "Second Recipe" }),
];
describe("Feed visibility for authenticated users", () => {
it("should show the feed (recipes) when user is logged in", async () => {
// Arrange: Mock Convex client to return recipes for the feed query
const convexClient = createMockConvexClientWithBehavior({
queryResults: {
// The key should match the actual recipes query signature
'recipes/getRecipes:{"paginationOpts":{"numItems":10,"cursor":null}}': { page: mockRecipes },
},
});
// Act: Render the Home page as an authenticated user
renderWithProviders(<Home />, {
authState: { isSignedIn: true, userId: "test-user-id", sessionId: "test-session-id" },
convexClient,
});
// Assert: The feed (recipes) should be visible (async)
expect(await screen.findByText("First Recipe")).toBeInTheDocument();
expect(await screen.findByText("Second Recipe")).toBeInTheDocument();
});
});
the corresponding react component:
"use client";
import React from "react";
import { Authenticated, Unauthenticated } from "convex/react";
import { SignInButton, UserButton } from "@clerk/nextjs";
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
export default function Home() {
return (
<>
<Authenticated>
<UserButton />
<Content />
</Authenticated>
<Unauthenticated>
<SignInButton />
</Unauthenticated>
</>
);
}
function Content() {
// Fetch published recipes (paginated)
const result = useQuery(api.recipes.getRecipes, { paginationOpts: { numItems: 10, cursor: null } });
const recipes = result?.page ?? [];
if (!recipes) return null;
return (
<div>
{recipes.map((recipe: { _id: string; title: string }) => (
<div key={recipe._id}>{recipe.title}</div>
))}
</div>
);
}
Can you say what outcome you're expecting vs what you're getting