I’m currently developing a unit test for a registration form in my application. The test should verify that the registration process completes successfully without storing new data in the actual database.
Here’s a simplified version of how my registration form and action work: Upon form submission, the registerAction function is invoked, which sanitizes the input data, validates it against a schema using Zod, checks if the email already exists in the database, hashes the password, and finally attempts to create a new user record in the database using Prisma.
During testing, I want to simulate a successful registration without modifying the database. What are the recommended strategies or best practices to achieve this goal?
register-form.tsx
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { FormSchema } from "@/schemas/FormSchema";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { useToast } from "@/components/ui/use-toast"
import { registerAction } from "@/actions/registerAction";
export function RegisterForm() {
const { toast } = useToast()
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
email: "",
password: "",
passwordConfirmation: "",
},
});
async function onSubmit(data: z.infer<typeof FormSchema>) {
try {
const result = await registerAction(data);
if (!result.success) {
toast({
title: "Error",
description: result.message
});
}
} catch (error) {
console.error(error);
}
}
return (
<Card className="max-w-sm w-full">
<CardHeader>
<CardTitle>Register</CardTitle>
<CardDescription>Enter your information to create an account.</CardDescription>
</CardHeader>
<CardContent>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="email"
type="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input {...field} type="password" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="passwordConfirmation"
render={({ field }) => (
<FormItem>
<FormLabel>Password Confirmation</FormLabel>
<FormControl>
<Input {...field} type="password" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full">
Submit
</Button>
</form>
</Form>
</CardContent>
</Card>
);
}
register-form.test.tsx
import '@testing-library/jest-dom'
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event';
import { RegisterForm } from './register-form'
describe('RegisterForm', () => {
test('submits form with valid data', async () => {
render(<RegisterForm />);
// Fill in form fields
const emailInput = screen.getByLabelText('Email');
const passwordInput = screen.getByLabelText('Password');
const passwordConfirmationInput = screen.getByLabelText('Password Confirmation');
userEvent.type(emailInput, '[email protected]');
userEvent.type(passwordInput, 'password123');
userEvent.type(passwordConfirmationInput, 'password123');
const submitButton = screen.getByRole('button', { name: 'Submit' });
fireEvent.click(submitButton);
});
});
registerAction.ts
"use server";
import { redirect } from "next/navigation";
import { z } from "zod";
import { FormSchema } from "@/schemas/FormSchema";
import prisma from "@/lib/prisma";
import bcrypt from "bcrypt";
function sanitizeData(data: { email: string; password: string; passwordConfirmation: string }): {
email: string;
password: string;
passwordConfirmation: string;
} {
const sanitizedData = {
email: data.email.trim().toLowerCase(),
password: data.password.trim(),
passwordConfirmation: data.passwordConfirmation.trim(),
};
return sanitizedData;
}
async function emailExists(email: string) {
try {
const user = await prisma.users.findUnique({
where: {
email: email,
},
});
if (user) {
return true;
}
} catch (error) {
console.error(error);
}
}
async function validateData(data: z.infer<typeof FormSchema>) {
const validation = FormSchema.safeParse({
email: data.email,
password: data.password,
passwordConfirmation: data.passwordConfirmation,
});
if (!validation.success) {
return validation.error.errors;
}
}
export const registerAction = async (data: z.infer<typeof FormSchema>) => {
try {
const sanitizedData = sanitizeData(data);
const validationErrors = await validateData(sanitizedData);
if (validationErrors) {
return {
success: false,
message: "Validation errors",
errors: validationErrors,
};
}
const emailAlreadyExist = await emailExists(sanitizedData.email);
if (emailAlreadyExist) {
return {
success: false,
message: "Email already exists",
};
}
const hashedPassword = await bcrypt.hash(sanitizedData.password, 10);
sanitizedData.password = hashedPassword;
await prisma.users.create({
data: {
email: sanitizedData.email,
password: sanitizedData.password,
},
});
return {
success: true,
message: "User created"
};
} catch (error) {
console.error(error);
}
redirect("/login");
};