npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@quramy/jest-prisma

v1.8.0

Published

[![github actions](https://github.com/Quramy/jest-prisma/workflows/build/badge.svg)](https://github.com/Quramy/jest-prisma/actions) [![npm version](https://badge.fury.io/js/@quramy%2Fjest-prisma.svg)](https://badge.fury.io/js/@quramy%2Fjest-prisma) [![Git

Downloads

18,217

Readme

jest-prisma

github actions npm version GitHub license

Jest environment for Prisma integrated testing. You can run each test case in isolated transaction which is rolled back automatically.

How to use

Install

$ npm i @quramy/jest-prisma -D

Configure Jest

/* jest.config.mjs */
export default {
  // ... Your jest configuration

  testEnvironment: "@quramy/jest-prisma/environment",
};

Configure TypeScript

/* tsconfig.json */

{
  "compilerOptions": {
    "types": ["@types/jest", "@quramy/jest-prisma"],
  }
}

Configure Prisma

jest-prisma uses Prisma interactive transaction feature. Interactive transaction needs to be listed in previewFeatures if you use @prisma/client < 4.7 .

Write tests

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);
  });
});

Configuration

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", () => {
  /* .... */
});

Use customized PrismaClient instance

By 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:

/* src/client.ts */
import { PrismaClient } from "@prisma/client";

export const prisma = new PrismaClient().$extends({
  client: {
    $myMethod: () => {
      /* ... */
    },
  },
});

You need configure jest-prisma by the following steps.

First, declare type of global.jestPrisma variable:

/* typeDefs/jest-prisma.d.ts */

import type { JestPrisma } from "@quramy/jest-prisma-core";
import type { prisma } from "../src/client";

declare global {
  var jestPrisma: JestPrisma<typeof prisma>;
}

And add the path of this declaration to your tsconfig.json:

/* tsconfig.json */

{
  "compilerOptions": {
    "types": ["@types/jest"], // You don't need list "@quramy/jest-prisma"
  },
  "includes": ["typeDefs/jest-prisma.d.ts"],
}

Finally, configure jest-prisma environment using setupFilesAfterEnv:

/* jest.config.mjs */

export default {
  testEnvironment: "@quramy/jest-prisma/environment",
  setupFilesAfterEnv: ["setupAfterEnv.ts"],
};
/* setupAfterEnv.ts */

import { prisma } from "./src/client";

jestPrisma.initializeClient(prisma);

Tips

Singleton

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
  });
});

DI Containers

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);
    ...
  });
});

Workaround for DateTime invocation error

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:

/* myEnv.ts */
import type { Circus } from "@jest/types";
import type { JestEnvironmentConfig, EnvironmentContext } from "@jest/environment";

import { PrismaEnvironmentDelegate } from "@quramy/jest-prisma-core";
import Environment from "jest-environment-node-single-context";

export default class PrismaEnvironment extends Environment {
  private readonly delegate: PrismaEnvironmentDelegate;

  constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
    super(config, context);
    this.delegate = new PrismaEnvironmentDelegate(config, context);
  }

  async setup() {
    const jestPrisma = await this.delegate.preSetup();
    await super.setup();
    this.global.jestPrisma = jestPrisma;
  }

  handleTestEvent(event: Circus.Event) {
    return this.delegate.handleTestEvent(event);
  }

  async teardown() {
    await Promise.all([super.teardown(), this.delegate.teardown()]);
  }
}
/* jest.config.mjs */

export default {
  testEnvironment: "myEnv.ts",
};

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.

Transaction Rollback

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.

/* jest.config.mjs */
export default {
  testEnvironment: "@quramy/jest-prisma/environment",
  testEnvironmentOptions: {
    enableExperimentalRollbackInTransaction: true, // <- add this
  },
};

Then, jest-prisma reproduces them in tests

const someTransaction = async prisma => {
  await prisma.$transaction(async p => {
    await p.user.create({
      data: {
        // ...
      },
    });

    throw new Error("Something failed. Affected changes will be rollback.");
  });
};

it("test", async () => {
  const prisma = jestPrisma.client;

  const before = await prisma.user.aggregate({ _count: true });
  expect(before._count).toBe(0);

  await someTransaction(prisma);

  const after = await prisma.user.aggregate({ _count: true });
  expect(after._count).toBe(0); // <- this will be 0
});

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.

References

global.jestPrisma

export interface JestPrisma<T = PrismaClientLike> {
  /**
   *
   * Primsa Client Instance whose transaction are isolated for each test case.
   * And this transaction is rolled back automatically after each test case.
   *
   */
  readonly client: T;

  /**
   *
   * You can call this from setupAfterEnv script and set your customized PrismaClient instance.
   *
   */
  readonly initializeClient: (client: unknown) => void;
}

Environment options

export interface JestPrismaEnvironmentOptions {
  /**
   *
   * If set true, each transaction is not rolled back but committed.
   *
   */
  readonly disableRollback?: boolean;

  /**
   *
   * If set to true, it will reproduce the rollback behavior when an error occurs at the point where the transaction is used.
   *
   * In particular, if you are using MongoDB as the Database connector, you must not set it to true.
   *
   */
  readonly enableExperimentalRollbackInTransaction?: boolean;

  /**
   *
   * Display SQL queries in test cases to STDOUT.
   *
   */
  readonly verboseQuery?: boolean;

  /**
   *
   * The maximum amount of time the Prisma Client will wait to acquire a transaction from the database.
   *
   * The default value is 5 seconds.
   *
   */
  readonly maxWait?: number;

  /**
   *
   * The maximum amount of time the interactive transaction can run before being canceled and rolled back.
   *
   * The default value is 5 seconds.
   *
   */
  readonly timeout?: number;

  /**
   *
   * Sets the transaction isolation level. By default this is set to the value currently configured in your database.
   *
   * @link https://www.prisma.io/docs/orm/prisma-client/queries/transactions#transaction-isolation-level
   *
   */
  readonly isolationLevel?: Prisma.TransactionIsolationLevel;

  /**
   *
   * Override the database connection URL.
   *
   * Useful if you have a separate database for testing.
   *
   */
  readonly databaseUrl?: string;
}

License

MIT