{"id":13432992,"url":"https://github.com/Quramy/jest-prisma","last_synced_at":"2025-03-17T10:33:02.072Z","repository":{"id":37410707,"uuid":"505691471","full_name":"Quramy/jest-prisma","owner":"Quramy","description":"Jest environment for integrated testing with Prisma client","archived":false,"fork":false,"pushed_at":"2025-03-11T19:38:53.000Z","size":672,"stargazers_count":285,"open_issues_count":20,"forks_count":16,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-16T14:04:07.771Z","etag":null,"topics":["integration-testing","jest","prisma"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Quramy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-21T04:40:19.000Z","updated_at":"2025-03-10T16:13:51.000Z","dependencies_parsed_at":"2024-02-14T01:27:49.254Z","dependency_job_id":"71b0bbec-0e8d-40b5-a3c9-52889d99a02e","html_url":"https://github.com/Quramy/jest-prisma","commit_stats":{"total_commits":196,"total_committers":12,"mean_commits":"16.333333333333332","dds":0.5153061224489797,"last_synced_commit":"113bf97d2f79028144ba777205cddbb841c87cea"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quramy%2Fjest-prisma","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quramy%2Fjest-prisma/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quramy%2Fjest-prisma/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quramy%2Fjest-prisma/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Quramy","download_url":"https://codeload.github.com/Quramy/jest-prisma/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243878478,"owners_count":20362433,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["integration-testing","jest","prisma"],"created_at":"2024-07-31T02:01:19.467Z","updated_at":"2025-03-17T10:33:02.041Z","avatar_url":"https://github.com/Quramy.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# jest-prisma\n\n[![github actions](https://github.com/Quramy/jest-prisma/workflows/build/badge.svg)](https://github.com/Quramy/jest-prisma/actions)\n[![npm version](https://badge.fury.io/js/@quramy%2Fjest-prisma.svg)](https://badge.fury.io/js/@quramy%2Fjest-prisma)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Quramy/jest-prisma/main/LICENSE.txt)\n\nJest environment for Prisma integrated testing.\nYou can run each test case in isolated transaction which is rolled back automatically.\n\n## How to use\n\n#### Install\n\n```sh\n$ npm i @quramy/jest-prisma -D\n```\n\n#### Configure Jest\n\n```js\n/* jest.config.mjs */\nexport default {\n  // ... Your jest configuration\n\n  testEnvironment: \"@quramy/jest-prisma/environment\",\n};\n```\n\n#### Configure TypeScript\n\n```js\n/* tsconfig.json */\n\n{\n  \"compilerOptions\": {\n    \"types\": [\"@types/jest\", \"@quramy/jest-prisma\"],\n  }\n}\n```\n\n#### Configure Prisma\n\njest-prisma uses [Prisma interactive transaction feature](https://www.prisma.io/docs/guides/performance-and-optimization/prisma-client-transactions-guide#interactive-transactions). Interactive transaction needs to be listed in `previewFeatures` if you use `@prisma/client` \u003c 4.7 .\n\n#### Write tests\n\nGlobal object `jestPrisma` is provided within jest-prisma environment. And Prisma client instance is available via `jestPrisma.client`\n\n```ts\ndescribe(UserService, () =\u003e {\n  // jestPrisma.client works with transaction rolled-back automatically after each test case end.\n  const prisma = jestPrisma.client;\n\n  test(\"Add user\", async () =\u003e {\n    const createdUser = await prisma.user.create({\n      data: {\n        id: \"001\",\n        name: \"quramy\",\n      },\n    });\n\n    expect(\n      await prisma.user.findFirst({\n        where: {\n          name: \"quramy\",\n        },\n      }),\n    ).toStrictEqual(createdUser);\n  });\n\n  test(\"Count user\", async () =\u003e {\n    expect(await prisma.user.count()).toBe(0);\n  });\n});\n```\n\n## Configuration\n\nYou can pass some options using `testEnvironmentOptions`.\n\n```js\n/* jest.config.mjs */\nexport default {\n  testEnvironment: \"@quramy/jest-prisma/environment\",\n  testEnvironmentOptions: {\n    verboseQuery: true,\n  },\n};\n```\n\nAlternatively, you can use `@jest-environment-options` pragma in your test file:\n\n```js\n/**\n *\n * @jest-environment-options: { \"verboseQuery\": true }\n *\n */\ntest(\"it should execute prisma client\", () =\u003e {\n  /* .... */\n});\n```\n\n## Use customized `PrismaClient` instance\n\nBy default, jest-prisma instantiates and uses Prisma client instance from `@prisma/client`.\n\nSometimes you want to use customized (or extended) Prisma client instance, such as:\n\n```ts\n/* src/client.ts */\nimport { PrismaClient } from \"@prisma/client\";\n\nexport const prisma = new PrismaClient().$extends({\n  client: {\n    $myMethod: () =\u003e {\n      /* ... */\n    },\n  },\n});\n```\n\nYou need configure jest-prisma by the following steps.\n\nFirst, declare type of `global.jestPrisma` variable:\n\n```ts\n/* typeDefs/jest-prisma.d.ts */\n\nimport type { JestPrisma } from \"@quramy/jest-prisma-core\";\nimport type { prisma } from \"../src/client\";\n\ndeclare global {\n  var jestPrisma: JestPrisma\u003ctypeof prisma\u003e;\n}\n```\n\nAnd add the path of this declaration to your tsconfig.json:\n\n```js\n/* tsconfig.json */\n\n{\n  \"compilerOptions\": {\n    \"types\": [\"@types/jest\"], // You don't need list \"@quramy/jest-prisma\"\n  },\n  \"includes\": [\"typeDefs/jest-prisma.d.ts\"],\n}\n```\n\nFinally, configure jest-prisma environment using `setupFilesAfterEnv`:\n\n```js\n/* jest.config.mjs */\n\nexport default {\n  testEnvironment: \"@quramy/jest-prisma/environment\",\n  setupFilesAfterEnv: [\"setupAfterEnv.ts\"],\n};\n```\n\n```js\n/* setupAfterEnv.ts */\n\nimport { prisma } from \"./src/client\";\n\njestPrisma.initializeClient(prisma);\n```\n\n## Tips\n\n### Singleton\n\nIf your project uses singleton Prisma client instance, such as:\n\n```ts\n/* src/client.ts */\nimport { PrismaClient } from \"@prisma/client\";\n\nexport const prisma = new PrismaClient();\n```\n\n```ts\n/* src/userService.ts */\n\nimport { prisma } from \"./client.ts\";\n\nexport function findUserById(id: string) {\n  const result = await prisma.user.findUnique({\n    where: { id },\n  });\n  return result;\n}\n```\n\nYou can replace the singleton instance to `jestPrisma.client` via `jest.mock`.\n\n```js\n/* setup-prisma.js */\n\njest.mock(\"./src/client\", () =\u003e {\n  return {\n    prisma: jestPrisma.client,\n  };\n});\n```\n\n```js\n/* jest.config.mjs */\nexport default {\n  testEnvironment: \"@quramy/jest-prisma/environment\",\n  setupFilesAfterEnv: [\"\u003crootDir\u003e/setup-prisma.js\"],\n};\n```\n\n```ts\nimport { prisma } from \"./client\";\n\nimport { findUserById } from \"./userService\";\n\ndescribe(\"findUserById\", () =\u003e {\n  beforeEach(async () =\u003e {\n    await prisma.user.create({\n      data: {\n        id: \"test_user_id\",\n      },\n    });\n  });\n\n  it(\"should return user\", async () =\u003e {\n    await findUserById(\"test_user_id\");\n    // assertion\n  });\n});\n```\n\n### DI Containers\n\nIf you're using DI containers such as [InversifyJS](https://github.com/inversify/InversifyJS) or [Awilix](https://github.com/jeffijoe/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.\n\nHere 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.\n\n```ts\n/* types.ts */\nexport const TYPES = {\n  PrismaClient: Symbol.for(\"PrismaClient\"),\n  UserRepository: Symbol.for(\"UserRepository\"),\n};\n```\n\n```ts\n/* user-repository.ts */\nimport { TYPES } from \"./types\";\n\ninterface IUserRepository {\n  findAll(): Promise\u003cUser[]\u003e;\n  findById(): Promise\u003cUser[]\u003e;\n  save(): Promise\u003cUser[]\u003e;\n}\n\n@injectable()\nclass UserRepositoryPrisma implements IUserRepository {\n  constructor(\n    @inject(TYPES.PrismaClient)\n    private readonly prisma: PrismaClient,\n  ) {}\n\n  async findAll() { .. }\n\n  async findById() { .. }\n\n  async save() { .. }\n}\n```\n\n```ts\n/* inversify.config.ts */\nimport { Container } from \"inversify\";\nimport { PrismaClient } from \"prisma\";\n\nimport { TYPES } from \"./types\";\nimport { UserRepositoryPrisma, IUserRepository } from \"./user-repository\";\n\nconst container = new Container();\n\ncontainer.bind(TYPES.PrismaClient).toConstantValue(new PrismaClient());\n\ncontainer.bind\u003cIUserRepository\u003e(TYPES.UserRepository).to(UserRepositoryPrisma);\n```\n\nIn 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)`.\n\nHowever, 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.\n\nNote 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.\n\n```ts\n/* user-repository.spec.ts */\ndescribe(\"UserRepository\", () =\u003e {\n  beforeEach(() =\u003e {\n    container\n      .rebind(TYPES.PrismaClient)\n      .toConstantValue(jestPrisma.client);\n  });\n\n  it(\"creates a user\" ,() =\u003e {\n    constainer.get\u003cIUserRepository\u003e(TYPES.UserRepository);\n    ...\n  });\n});\n```\n\n### Workaround for DateTime invocation error\n\nIf you encounter errors like the following:\n\n```\nArgument gte: Got invalid value {} on prisma.findFirstUser. Provided Json, expected DateTime.\n```\n\nIt's because that Jest global `Date` is differ from JavaScript original `Date`(https://github.com/facebook/jest/issues/2549).\n\nAnd this error can be work around by using [single context environment](https://www.npmjs.com/package/jest-environment-node-single-context):\n\n```ts\n/* myEnv.ts */\nimport type { Circus } from \"@jest/types\";\nimport type { JestEnvironmentConfig, EnvironmentContext } from \"@jest/environment\";\n\nimport { PrismaEnvironmentDelegate } from \"@quramy/jest-prisma-core\";\nimport Environment from \"jest-environment-node-single-context\";\n\nexport default class PrismaEnvironment extends Environment {\n  private readonly delegate: PrismaEnvironmentDelegate;\n\n  constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {\n    super(config, context);\n    this.delegate = new PrismaEnvironmentDelegate(config, context);\n  }\n\n  async setup() {\n    const jestPrisma = await this.delegate.preSetup();\n    await super.setup();\n    this.global.jestPrisma = jestPrisma;\n  }\n\n  handleTestEvent(event: Circus.Event) {\n    return this.delegate.handleTestEvent(event);\n  }\n\n  async teardown() {\n    await Promise.all([super.teardown(), this.delegate.teardown()]);\n  }\n}\n```\n\n```js\n/* jest.config.mjs */\n\nexport default {\n  testEnvironment: \"myEnv.ts\",\n};\n```\n\nCaveat: This work around might me affect your test cases using Jest fake timer features.\n\nSee also https://github.com/Quramy/jest-prisma/issues/56.\n\n### Transaction Rollback\n\nIf you are using [$transaction callbacks in Prisma with the feature to roll back in case of an error](https://www.prisma.io/docs/concepts/components/prisma-client/transactions#:~:text=If%20your%20application%20encounters%20an%20error%20along%20the%20way%2C%20the%20async%20function%20will%20throw%20an%20exception%20and%20automatically%20rollback%20the%20transaction.), that's ok too. :D\n\nSet `enableExperimentalRollbackInTransaction` in testEnvironmentOptions to `true`. This option allows nested transaction.\n\n```js\n/* jest.config.mjs */\nexport default {\n  testEnvironment: \"@quramy/jest-prisma/environment\",\n  testEnvironmentOptions: {\n    enableExperimentalRollbackInTransaction: true, // \u003c- add this\n  },\n};\n```\n\nThen, jest-prisma reproduces them in tests\n\n```ts\nconst someTransaction = async prisma =\u003e {\n  await prisma.$transaction(async p =\u003e {\n    await p.user.create({\n      data: {\n        // ...\n      },\n    });\n\n    throw new Error(\"Something failed. Affected changes will be rollback.\");\n  });\n};\n\nit(\"test\", async () =\u003e {\n  const prisma = jestPrisma.client;\n\n  const before = await prisma.user.aggregate({ _count: true });\n  expect(before._count).toBe(0);\n\n  await someTransaction(prisma);\n\n  const after = await prisma.user.aggregate({ _count: true });\n  expect(after._count).toBe(0); // \u003c- this will be 0\n});\n```\n\n\u003e [!TIP]\n\u003e 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.\n\nInternally, SAVEPOINT, which is formulated in the Standard SQL, is used.\n\nUnfortunately, 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.\n\n## References\n\n### `global.jestPrisma`\n\n```ts\nexport interface JestPrisma\u003cT = PrismaClientLike\u003e {\n  /**\n   *\n   * Primsa Client Instance whose transaction are isolated for each test case.\n   * And this transaction is rolled back automatically after each test case.\n   *\n   */\n  readonly client: T;\n\n  /**\n   *\n   * You can call this from setupAfterEnv script and set your customized PrismaClient instance.\n   *\n   */\n  readonly initializeClient: (client: unknown) =\u003e void;\n}\n```\n\n### Environment options\n\n```ts\nexport interface JestPrismaEnvironmentOptions {\n  /**\n   *\n   * If set true, each transaction is not rolled back but committed.\n   *\n   */\n  readonly disableRollback?: boolean;\n\n  /**\n   *\n   * If set to true, it will reproduce the rollback behavior when an error occurs at the point where the transaction is used.\n   *\n   * In particular, if you are using MongoDB as the Database connector, you must not set it to true.\n   *\n   */\n  readonly enableExperimentalRollbackInTransaction?: boolean;\n\n  /**\n   *\n   * Display SQL queries in test cases to STDOUT.\n   *\n   */\n  readonly verboseQuery?: boolean;\n\n  /**\n   *\n   * The maximum amount of time the Prisma Client will wait to acquire a transaction from the database.\n   *\n   * The default value is 5 seconds.\n   *\n   */\n  readonly maxWait?: number;\n\n  /**\n   *\n   * The maximum amount of time the interactive transaction can run before being canceled and rolled back.\n   *\n   * The default value is 5 seconds.\n   *\n   */\n  readonly timeout?: number;\n\n  /**\n   *\n   * Sets the transaction isolation level. By default this is set to the value currently configured in your database.\n   *\n   * @link https://www.prisma.io/docs/orm/prisma-client/queries/transactions#transaction-isolation-level\n   *\n   */\n  readonly isolationLevel?: Prisma.TransactionIsolationLevel;\n\n  /**\n   *\n   * Override the database connection URL.\n   *\n   * Useful if you have a separate database for testing.\n   *\n   */\n  readonly databaseUrl?: string;\n}\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FQuramy%2Fjest-prisma","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FQuramy%2Fjest-prisma","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FQuramy%2Fjest-prisma/lists"}