Gathering detailed insights and metrics for @quramy/jest-prisma-core
Gathering detailed insights and metrics for @quramy/jest-prisma-core
Gathering detailed insights and metrics for @quramy/jest-prisma-core
Gathering detailed insights and metrics for @quramy/jest-prisma-core
Jest environment for integrated testing with Prisma client
npm install @quramy/jest-prisma-core
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
278 Stars
319 Commits
16 Forks
5 Watching
7 Branches
11 Contributors
Updated on 26 Nov 2024
TypeScript (97.72%)
JavaScript (1.76%)
Shell (0.52%)
Cumulative downloads
Total Downloads
Last day
-5%
5,294
Compared to previous day
Last week
3.5%
28,332
Compared to previous week
Last month
21.6%
109,579
Compared to previous month
Last year
151%
760,631
Compared to previous year
Jest environment for Prisma integrated testing. You can run each test case in isolated transaction which is rolled back automatically.
1$ npm i @quramy/jest-prisma -D
1/* jest.config.mjs */ 2export default { 3 // ... Your jest configuration 4 5 testEnvironment: "@quramy/jest-prisma/environment", 6};
1/* tsconfig.json */ 2 3{ 4 "compilerOptions": { 5 "types": ["@types/jest", "@quramy/jest-prisma"], 6 } 7}
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
1describe(UserService, () => { 2 // jestPrisma.client works with transaction rolled-back automatically after each test case end. 3 const prisma = jestPrisma.client; 4 5 test("Add user", async () => { 6 const createdUser = await prisma.user.create({ 7 data: { 8 id: "001", 9 name: "quramy", 10 }, 11 }); 12 13 expect( 14 await prisma.user.findFirst({ 15 where: { 16 name: "quramy", 17 }, 18 }), 19 ).toStrictEqual(createdUser); 20 }); 21 22 test("Count user", async () => { 23 expect(await prisma.user.count()).toBe(0); 24 }); 25});
You can pass some options using testEnvironmentOptions
.
1/* jest.config.mjs */ 2export default { 3 testEnvironment: "@quramy/jest-prisma/environment", 4 testEnvironmentOptions: { 5 verboseQuery: true, 6 }, 7};
Alternatively, you can use @jest-environment-options
pragma in your test file:
1/** 2 * 3 * @jest-environment-options: { "verboseQuery": true } 4 * 5 */ 6test("it should execute prisma client", () => { 7 /* .... */ 8});
PrismaClient
instanceBy default, jest-prisma instantiates and uses Prisma client instance from @prisma/client
.
Sometimes you want to use customized (or extended) Prisma client instance, such as:
1/* src/client.ts */ 2import { PrismaClient } from "@prisma/client"; 3 4export const prisma = new PrismaClient().$extends({ 5 client: { 6 $myMethod: () => { 7 /* ... */ 8 }, 9 }, 10});
You need configure jest-prisma by the following steps.
First, declare type of global.jestPrisma
variable:
1/* typeDefs/jest-prisma.d.ts */ 2 3import type { JestPrisma } from "@quramy/jest-prisma-core"; 4import type { prisma } from "../src/client"; 5 6declare global { 7 var jestPrisma: JestPrisma<typeof prisma>; 8}
And add the path of this declaration to your tsconfig.json:
1/* tsconfig.json */ 2 3{ 4 "compilerOptions": { 5 "types": ["@types/jest"], // You don't need list "@quramy/jest-prisma" 6 }, 7 "includes": ["typeDefs/jest-prisma.d.ts"], 8}
Finally, configure jest-prisma environment using setupFilesAfterEnv
:
1/* jest.config.mjs */ 2 3export default { 4 testEnvironment: "@quramy/jest-prisma/environment", 5 setupFilesAfterEnv: ["setupAfterEnv.ts"], 6};
1/* setupAfterEnv.ts */ 2 3import { prisma } from "./src/client"; 4 5jestPrisma.initializeClient(prisma);
If your project uses singleton Prisma client instance, such as:
1/* src/client.ts */ 2import { PrismaClient } from "@prisma/client"; 3 4export const prisma = new PrismaClient();
1/* src/userService.ts */ 2 3import { prisma } from "./client.ts"; 4 5export function findUserById(id: string) { 6 const result = await prisma.user.findUnique({ 7 where: { id }, 8 }); 9 return result; 10}
You can replace the singleton instance to jestPrisma.client
via jest.mock
.
1/* setup-prisma.js */ 2 3jest.mock("./src/client", () => { 4 return { 5 prisma: jestPrisma.client, 6 }; 7});
1/* jest.config.mjs */ 2export default { 3 testEnvironment: "@quramy/jest-prisma/environment", 4 setupFilesAfterEnv: ["<rootDir>/setup-prisma.js"], 5};
1import { prisma } from "./client"; 2 3import { findUserById } from "./userService"; 4 5describe("findUserById", () => { 6 beforeEach(async () => { 7 await prisma.user.create({ 8 data: { 9 id: "test_user_id", 10 }, 11 }); 12 }); 13 14 it("should return user", async () => { 15 await findUserById("test_user_id"); 16 // assertion 17 }); 18});
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.
1/* types.ts */ 2export const TYPES = { 3 PrismaClient: Symbol.for("PrismaClient"), 4 UserRepository: Symbol.for("UserRepository"), 5};
1/* user-repository.ts */ 2import { TYPES } from "./types"; 3 4interface IUserRepository { 5 findAll(): Promise<User[]>; 6 findById(): Promise<User[]>; 7 save(): Promise<User[]>; 8} 9 10@injectable() 11class UserRepositoryPrisma implements IUserRepository { 12 constructor( 13 @inject(TYPES.PrismaClient) 14 private readonly prisma: PrismaClient, 15 ) {} 16 17 async findAll() { .. } 18 19 async findById() { .. } 20 21 async save() { .. } 22}
1/* inversify.config.ts */ 2import { Container } from "inversify"; 3import { PrismaClient } from "prisma"; 4 5import { TYPES } from "./types"; 6import { UserRepositoryPrisma, IUserRepository } from "./user-repository"; 7 8const container = new Container(); 9 10container.bind(TYPES.PrismaClient).toConstantValue(new PrismaClient()); 11 12container.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.
1/* user-repository.spec.ts */ 2describe("UserRepository", () => { 3 beforeEach(() => { 4 container 5 .rebind(TYPES.PrismaClient) 6 .toConstantValue(jestPrisma.client); 7 }); 8 9 it("creates a user" ,() => { 10 constainer.get<IUserRepository>(TYPES.UserRepository); 11 ... 12 }); 13});
If you encounter errors like the following:
Argument gte: Got invalid value {} on prisma.findFirstUser. Provided Json, expected DateTime.
It's because that Jest global Date
is differ from JavaScript original Date
(https://github.com/facebook/jest/issues/2549).
And this error can be work around by using single context environment:
1/* myEnv.ts */ 2import type { Circus } from "@jest/types"; 3import type { JestEnvironmentConfig, EnvironmentContext } from "@jest/environment"; 4 5import { PrismaEnvironmentDelegate } from "@quramy/jest-prisma-core"; 6import Environment from "jest-environment-node-single-context"; 7 8export default class PrismaEnvironment extends Environment { 9 private readonly delegate: PrismaEnvironmentDelegate; 10 11 constructor(config: JestEnvironmentConfig, context: EnvironmentContext) { 12 super(config, context); 13 this.delegate = new PrismaEnvironmentDelegate(config, context); 14 } 15 16 async setup() { 17 const jestPrisma = await this.delegate.preSetup(); 18 await super.setup(); 19 this.global.jestPrisma = jestPrisma; 20 } 21 22 handleTestEvent(event: Circus.Event) { 23 return this.delegate.handleTestEvent(event); 24 } 25 26 async teardown() { 27 await Promise.all([super.teardown(), this.delegate.teardown()]); 28 } 29}
1/* jest.config.mjs */ 2 3export default { 4 testEnvironment: "myEnv.ts", 5};
Caveat: This work around might me affect your test cases using Jest fake timer features.
See also https://github.com/Quramy/jest-prisma/issues/56.
If you are using $transaction callbacks in Prisma with the feature to roll back in case of an error, that's ok too. :D
Set enableExperimentalRollbackInTransaction
in testEnvironmentOptions to true
. This option allows nested transaction.
1/* jest.config.mjs */ 2export default { 3 testEnvironment: "@quramy/jest-prisma/environment", 4 testEnvironmentOptions: { 5 enableExperimentalRollbackInTransaction: true, // <- add this 6 }, 7};
Then, jest-prisma reproduces them in tests
1const someTransaction = async prisma => { 2 await prisma.$transaction(async p => { 3 await p.user.create({ 4 data: { 5 // ... 6 }, 7 }); 8 9 throw new Error("Something failed. Affected changes will be rollback."); 10 }); 11}; 12 13it("test", async () => { 14 const prisma = jestPrisma.client; 15 16 const before = await prisma.user.aggregate({ _count: true }); 17 expect(before._count).toBe(0); 18 19 await someTransaction(prisma); 20 21 const after = await prisma.user.aggregate({ _count: true }); 22 expect(after._count).toBe(0); // <- this will be 0 23});
[!TIP] The nested transaction is used to suppress PostgreSQL's
current transaction is aborted commands ignored until end of transaction block
error. See https://github.com/Quramy/jest-prisma/issues/141 if you want more details.
Internally, SAVEPOINT, which is formulated in the Standard SQL, is used.
Unfortunately, however, MongoDB does not support partial rollbacks within a Transaction using SAVEPOINT, so MongoDB is not able to reproduce rollbacks. In this case, do not set enableExperimentalRollbackInTransaction
to true.
global.jestPrisma
1export interface JestPrisma<T = PrismaClientLike> { 2 /** 3 * 4 * Primsa Client Instance whose transaction are isolated for each test case. 5 * And this transaction is rolled back automatically after each test case. 6 * 7 */ 8 readonly client: T; 9 10 /** 11 * 12 * You can call this from setupAfterEnv script and set your customized PrismaClient instance. 13 * 14 */ 15 readonly initializeClient: (client: unknown) => void; 16}
1export interface JestPrismaEnvironmentOptions { 2 /** 3 * 4 * If set true, each transaction is not rolled back but committed. 5 * 6 */ 7 readonly disableRollback?: boolean; 8 9 /** 10 * 11 * If set to true, it will reproduce the rollback behavior when an error occurs at the point where the transaction is used. 12 * 13 * In particular, if you are using MongoDB as the Database connector, you must not set it to true. 14 * 15 */ 16 readonly enableExperimentalRollbackInTransaction?: boolean; 17 18 /** 19 * 20 * Display SQL queries in test cases to STDOUT. 21 * 22 */ 23 readonly verboseQuery?: boolean; 24 25 /** 26 * 27 * The maximum amount of time the Prisma Client will wait to acquire a transaction from the database. 28 * 29 * The default value is 5 seconds. 30 * 31 */ 32 readonly maxWait?: number; 33 34 /** 35 * 36 * The maximum amount of time the interactive transaction can run before being canceled and rolled back. 37 * 38 * The default value is 5 seconds. 39 * 40 */ 41 readonly timeout?: number; 42 43 /** 44 * 45 * Sets the transaction isolation level. By default this is set to the value currently configured in your database. 46 * 47 * @link https://www.prisma.io/docs/orm/prisma-client/queries/transactions#transaction-isolation-level 48 * 49 */ 50 readonly isolationLevel?: Prisma.TransactionIsolationLevel; 51 52 /** 53 * 54 * Override the database connection URL. 55 * 56 * Useful if you have a separate database for testing. 57 * 58 */ 59 readonly databaseUrl?: string; 60}
MIT
No vulnerabilities found.
No security vulnerabilities found.