Jest environment for Prisma integrated testing. You can run each test case in isolated transaction which is rolled back automatically.
$ npm i @quramy/jest-prisma -D
/* jest.config.mjs */
export default {
// ... Your jest configuration
testEnvironment: "@quramy/jest-prisma/environment",
};
/* tsconfig.json */
{
"compilerOptions": {
"types": ["@types/jest", "@quramy/jest-prisma"],
}
}
jest-prisma uses Prisma interactive transaction feature. Interactive transaction needs to be listed in previewFeatures
if you use @prisma/client
< 4.7 .
Global object jestPrisma
is provided within jest-prisma environment. And Prisma client instance is available via jestPrisma.client
describe(UserService, () => {
// jestPrisma.client works with transaction rolled-back automatically after each test case end.
const prisma = jestPrisma.client;
test("Add user", async () => {
const createdUser = await prisma.user.create({
data: {
id: "001",
name: "quramy",
},
});
expect(
await prisma.user.findFirst({
where: {
name: "quramy",
},
}),
).toStrictEqual(createdUser);
});
test("Count user", async () => {
expect(await prisma.user.count()).toBe(0);
});
});
You can pass some options using testEnvironmentOptions
.
/* jest.config.mjs */
export default {
testEnvironment: "@quramy/jest-prisma/environment",
testEnvironmentOptions: {
verboseQuery: true,
},
};
Alternatively, you can use @jest-environment-options
pragma in your test file:
/**
*
* @jest-environment-options: { "verboseQuery": true }
*
*/
test("it should execute prisma client", () => {
/* .... */
});
If your project uses singleton Prisma client instance, such as:
/* src/client.ts */
import { PrismaClient } from "@prisma/client";
export const prisma = new PrismaClient();
/* src/userService.ts */
import { prisma } from "./client.ts";
export function findUserById(id: string) {
const result = await prisma.user.findUnique({
where: { id },
});
return result;
}
You can replace the singleton instance to jestPrisma.client
via jest.mock
.
/* setup-prisma.js */
jest.mock("./src/client", () => {
return {
prisma: jestPrisma.client,
};
});
/* jest.config.mjs */
export default {
testEnvironment: "@quramy/jest-prisma/environment",
setupFilesAfterEnv: ["<rootDir>/setup-prisma.js"],
};
import { prisma } from "./client";
import { findUserById } from "./userService";
describe("findUserById", () => {
beforeEach(async () => {
await prisma.user.create({
data: {
id: "test_user_id",
},
});
});
it("should return user", async () => {
await findUserById("test_user_id");
// assertion
});
});
If you're using DI containers such as InversifyJS or Awilix and wish to introduce jest-prisma, you can easily do that just by rebinding PrismaClient to a global jestPrisma
instance provided by jest-prisma.
Here is an example below. Given that we have the following repository. Note that it is decorated by @injectable
so will prisma
will be inject as a constructor argument.
/* types.ts */
export const TYPES = {
PrismaClient: Symbol.for("PrismaClient"),
UserRepository: Symbol.for("UserRepository"),
};
/* user-repository.ts */
import { TYPES } from "./types";
interface IUserRepository {
findAll(): Promise<User[]>;
findById(): Promise<User[]>;
save(): Promise<User[]>;
}
@injectable()
class UserRepositoryPrisma implements IUserRepository {
constructor(
@inject(TYPES.PrismaClient)
private readonly prisma: PrismaClient,
) {}
async findAll() { .. }
async findById() { .. }
async save() { .. }
}
/* inversify.config.ts */
import { Container } from "inversify";
import { PrismaClient } from "prisma";
import { TYPES } from "./types";
import { UserRepositoryPrisma, IUserRepository } from "./user-repository";
const container = new Container();
container.bind(TYPES.PrismaClient).toConstantValue(new PrismaClient());
container.bind<IUserRepository>(TYPES.UserRepository).to(UserRepositoryPrisma);
In most cases, the setup above allows you to inject a pre-configured PrismaClient
by associating the symbol to an actual instance like bind(TYPES.PrismaClient).toConstantValue(new PrismaClient())
and then acquire the repository by get(TYPES.UserRepository)
.
However, with jest-prisma, the global jestPrisma.client
object is initialised for each unit tests so you have to make sure that you're binding the instance after the initialisation.
Note that we're rebinding PrismaClient to the jest-prisma inside beforeEach
phase. Any other phase including beforeAll
or setupFilesAfterEnv
may not work as you expect.
/* user-repository.spec.ts */
describe("UserRepository", () => {
beforeEach(() => {
container
.rebind(TYPES.PrismaClient)
.toConstantValue(jestPrisma.client);
});
it("creates a user" ,() => {
constainer.get<IUserRepository>(TYPES.UserRepository);
...
});
});
export interface JestPrisma {
/**
*
* Primsa Client Instance whose transaction are isolated for each test case.
* And this transaction is rolled back automatically after each test case.
*
*/
readonly client: PrismaClient;
}
export interface JestPrismaEnvironmentOptions {
/**
*
* If set true, each transaction is not rolled back but committed.
*
*/
readonly disableRollback?: boolean;
/**
*
* Display SQL queries in test cases to STDOUT.
*
*/
readonly verboseQuery?: boolean;
/**
*
* Override the database connection URL.
*
* Useful if you have a separate database for testing.
*
*/
readonly databaseUrl?: string;
}
MIT