GithubHelp home page GithubHelp logo

quramy / jest-prisma Goto Github PK

View Code? Open in Web Editor NEW
244.0 5.0 16.0 642 KB

Jest environment for integrated testing with Prisma client

License: MIT License

JavaScript 1.76% TypeScript 97.72% Shell 0.52%
integration-testing jest prisma

jest-prisma's Introduction

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(jestjs/jest#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 #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. This option allows nested transaction.

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

Tip

The nested transaction is used to suppress PostgreSQL's current transaction is aborted commands ignored until end of transaction block error. See #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.

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

jest-prisma's People

Contributors

aiji42 avatar danielpowell4 avatar dowanna avatar emeitch avatar germanescobar avatar kz-d avatar mishio-n avatar neet avatar pranas avatar quramy avatar renovate[bot] avatar yutaura avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

jest-prisma's Issues

Too many Prisma clients being started => jest exits with an error code

We've been seeing the following error in our tests, and I was assuming it would go away when we add jest-prisma:

 console.warn
      warn(prisma-client) There are already 10 instances of Prisma Client actively running.

      10 |
      11 |   constructor(configService: ConfigService) {
    > 12 |     super({
         |     ^
      13 |       datasources: {
      14 |         db: { url: <string>configService.get<string>('DATABASE_URL') },
      15 |       },

      at zt.checkForTooManyEngines (node_modules/@prisma/client/runtime/library.js:100:664)
      at new LibraryEngine (node_modules/@prisma/client/runtime/library.js:100:495)
      at PrismaService.getEngine (node_modules/@prisma/client/runtime/library.js:177:6129)
      at new PrismaClient (node_modules/@prisma/client/runtime/library.js:177:5713)
      at new PrismaService (prisma/prisma.service.ts:12:5)
      at TestingInjector.instantiateClass (node_modules/@nestjs/core/injector/injector.js:330:19)
      at callback (node_modules/@nestjs/core/injector/injector.js:48:41)
....
Test Suites: 22 passed, 22 total
Tests:       676 passed, 676 total
Snapshots:   0 total
Time:        17.307 s, estimated 25 s

Unfortunately, it still happens, although with a different backtrace:

 ●  Cannot log after tests are done. Did you forget to wait for something async in your test?
    Attempted to log "warn(prisma-client) This is the 10th instance of Prisma Client being started. Make sure this is intentional.".

      26 |   constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {
      27 |     super(config, context);
    > 28 |     this.delegate = new PrismaEnvironmentDelegate(config, context);
         |                         ^
      29 |   }
      30 |
      31 |   override async setup() {

      at console.warn (node_modules/@jest/console/build/BufferedConsole.js:191:10)
      at Dt.checkForTooManyEngines (node_modules/@prisma/client/runtime/library.js:102:893)
      at new Dt (node_modules/@prisma/client/runtime/library.js:102:826)
      at t.getEngine (node_modules/@prisma/client/runtime/library.js:126:5173)
      at new t (node_modules/@prisma/client/runtime/library.js:126:4737)
      at new PrismaEnvironmentDelegate (node_modules/@quramy/jest-prisma-core/lib/delegate.js:19:32)
      at new PrismaEnvironment (test/jestSingleContextEnv.ts:28:25)

The problem seems to be that Prisma never decreases the counter of Client instances. it keeps increasing it forever, and it does so every time PrismaEnvironmentDelegate is instantiated because of this line:

const originalClient = new PrismaClient({

Is there a way to reuse the client between environments?

This happens with the following packages (filtered out irrelevant ones):

    "@prisma/client": "^5.1.1",
    "@quramy/jest-prisma-core": "^1.5.0",
    "@quramy/jest-prisma-node": "^1.5.0",
    "@types/jest": "^29.5.3",
    "@types/node": "16",
    "jest": "^29.6.2",
    "jest-environment-node-single-context": "^29.1.0",
    "jest-mock-extended": "^3.0.5",
    "nock": "^13.3.2",
    "prisma": "^5.1.1",
    "supertest": "^6.3.3",
    "ts-jest": "^29.1.1",
    "ts-loader": "^9.4.4",
    "ts-node": "^10.9.1",
    "tsconfig-paths": "^4.2.0",
    "typescript": "^5.1.6"

I would be happy to keep looking for a workaround, but I'd be grateful for a nudge in the right direction. I lack the following to continue:

  • How often does PrismaEnvironmentDelegate gets created? Is it per test suite? Is it done in parallel?
  • Is there a way for PrismaEnvironmentDelegate to reuse the same client between instances?
  • There's a possibility that some of our tests are still not stubbed given that we use Nest's Dependency Injection. Is there an easy way to find the places where that one gets instantiated? I tried throwing from the constructor, but it didn't fail any of the tests.
  • Log message implies this happens after the tests are done. Is it because of buffering, or because of a test that is not properly awaited?

In any case, thanks for all the effort that went into this package, apart from this it works like a charm!

[Error] Can not transpile , type definition error for PrismaClient at delegate.d.ts

When I try to use this library, I got transplile error at delegate.d.ts.

This seems to be wrong type definition

getClient(): PrismaClient<Prisma.PrismaClientOptions, never, Prisma.RejectOnNotFound | Prisma.RejectPerOperation | undefined, {
        result: {};
        model: {};
        query: {};
        client: {};
    }> | undefined;

Error Message

Generic type 'PrismaClient<T, U, GlobalReject>' requires between 0 and 3 type arguments.

As you can see, the definition has one more extra arguments...

Do I have any options to remove error?

Unable to Use jestPrisma as a Global Object in Test Environment with Blitz.js

Description

I tried to set up the jest-prisma environment with a Blitz.js app, following the documentation, but encountered an issue where jestPrisma is not defined in the test environment.

Code and Configuration

jest.config.js

module.exports = {
  preset: "blitz",
  testEnvironment: "@quramy/jest-prisma/environment",
}

tsconfig.js

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

the test I wrote

describe("jest-prisma test",()=>{
  it("should be able to use jest-prisma",async()=>{
    const data = {
        name:"test venue",
        email: '[email protected]'
      }
    await jestPrisma.client.user.create({
      data,
    })
    const result = await jestPrisma.client.user.findMany()
    expect(result).toEqual([data])
  })
})

Expected Behavior

jestPrisma should be provided as a global object and the test should run without issues.

Actual Behavior

I encountered a ReferenceError when running the test:

ReferenceError: jestPrisma is not defined

Version Info

@prisma/client: 3.9.2 (Using previewFeatures: ["interactiveTransactions", "referentialActions"])
blitz: 0.44.3
Node.js: 14.21.3
OS: Mac 12.6

jest-prisma 1.7.0 Type Definition of `global.jestPrisma` May Result in Unexpected `any`

With the 1.7.0 release of @quramy/jest-prisma, there was a change to allow for customized jest prisma clients. This change included modification of the TypeScript type definition of jestPrisma to use a newly-created JestPrisma generic:

declare global {
var jestPrisma: JestPrisma<PrismaClient>;
}

That JestPrisma definition comes from a different package: @quramy/jest-prisma-core. The trouble here is that if you're upgrading an existing project, NPM has a terrible (incorrect, poor, dumb) habit of not updating sub-dependencies. This results in the 1.7.0 release of jest-prisma running against an old (in my case 1.5.0) release of jest-prisma-core. This results in the exported JestPrisma type not being a generic and causing the TypeScript system to infer jestPrisma as an any type.

In this case, the correct solution is likely to update the jest-prisma-core sub-dependency requirement to be ^1.7.0 rather than ^1.0.0 because it is no longer compatible below that.

Does middleware not support?

I wrote code to test the middleware of Prisma.

according to the guide, I mocked the test code with the code below.

jest.mock("@/server/db/client", () => {
  return {
    prisma: jestPrisma.client,
  };
});

and the client file is written like this.

import { PrismaClient } from "@prisma/client";

import { env } from "../../env/server.mjs";
import softDeleteMiddleware from "./middleware/softDelete";

declare global {
  // eslint-disable-next-line no-var
  var prisma: PrismaClient | undefined;
}

const prisma =
  global.prisma ||
  new PrismaClient({
    log:
      env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
  });

if (env.NODE_ENV !== "production") {
  global.prisma = prisma;
}

prisma.$use(softDeleteMiddleware);

export { prisma };

the middleware is the same as the code from here.

the setup was also done according to the guide, and the test codes without middleware applied worked well.

Test Code

test("exists row when delete", async () => {
    const user = await userRepo.createUser("test", "[email protected]");
    await userRepo.deleteUser(user.id);

    const result = await userRepo.findUserById(user.id);

    expect(result).not.toBeNull();
    expect(result?.deletedAt).not.toBeNull();
  });

does middleware not support? Or I wonder if it doesn't work because I set it up wrong.

Add option for TransactionIsolationLevel setting

First up: thanks for this awesome library. It has transformed my day-to-day in a great way!

I'm wondering if you are interested in a pull request to expose 'isloationLevel' as a setting that can be defined for transactions

As context, we're seeing some deadlocks in our growing test suites due to various tests and factories trying to create default users in our test setup. I believe that changing from the postgres default of 'Read Committed' to 'Serializable' would help us

As of a few days ago, Prisma itself merged a PR to allow these transaction options to be set on the PrismaClient via a new transactionOptions setting per prisma/prisma#19592

I believe it would most need to be added here with a safe default of 'undefined'

{
maxWait: this.options.maxWait ?? DEFAULT_MAX_WAIT,
timeout: this.options.timeout ?? DEFAULT_TIMEOUT,
},

More reading:
https://www.prisma.io/docs/orm/prisma-client/queries/transactions#transaction-isolation-level

Prisma support + database defaults:
https://www.prisma.io/docs/orm/prisma-client/queries/transactions#supported-isolation-levels

Invalid variable access: jestPrisma

In the documentation it says

/* setup-prisma.js */

jest.mock("./src/client", () => {
  return {
    prisma: jestPrisma.client,
  };
});

When running npm test it says:

    ReferenceError: /Users/myname/w/myrepo/setup-prisma.js: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
    Invalid variable access: jestPrisma

jest.config.js looks like:

module.exports = {
  clearMocks: true,
  preset: './jest-preset',
  testEnvironment: "@quramy/jest-prisma/environment",
  testEnvironmentOptions: {
    verboseQuery: true,
  },
  setupFilesAfterEnv: ["./setup-prisma.js"],
}

It seems there is a problem setting up jestPrisma to be accessible from the 'setup.prisma.js' file?

I tried adding const { jestPrisma } = require('@quramy/jest-prisma'); to the top of 'setup.prisma.js' and it still gave the same error.

const { jestPrisma } = require('@quramy/jest-prisma');

jest.mock("./lib/prisma", () => {
  return {
    prisma: jestPrisma.client,
  };
});

`enableExperimentalRollbackInTransaction` seems not working with MySQL

Hello,

Really nice library!

Run into an issue with enableExperimentalRollbackInTransaction flag. My environment:

MySQL v8.0.34
Prisma v5.1.1

I'm trying to test the endpoint with the transaction inside and set enableExperimentalRollbackInTransaction = true. However, I get a MySQL 1295 error when entering transaction code in the endpoint during test.

{
  error: PrismaClientKnownRequestError: 
  Invalid `prisma.$executeRawUnsafe()` invocation:
        
        
  Raw query failed. Code: `1295`. Message: `This command is not supported in the prepared statement protocol yet`
    at Hr.handleRequestError (/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:6999)
    at Hr.handleAndLogRequestError (/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:6388)
    at Hr.request (/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:122:6108)
    at l (/node_modules/.pnpm/@[email protected][email protected]/node_modules/@prisma/client/runtime/library.js:126:10298)
    at OfficeService.createOffice (/api/src/services/OfficeService.ts:83:5)
    at Object.<anonymous> (/api/src/tests/Office/createOffice.integration.test.ts:71:7) {
  code: 'P2010',
  clientVersion: '5.1.1',
  meta: {
    code: '1295',
    message: 'This command is not supported in the prepared statement protocol yet'
    }
  }
}

Error points to the code

await parentTxClient.$executeRawUnsafe(`SAVEPOINT ${savePointId};`);

In MySQL, there are docs with SQL Syntax Permitted in Prepared Statements and it seems that SAVEPOINT is not on this list. My guess would be because of that we're not able to run $executeRawUnsafe properly. But maybe I did something wrong in the process. Did you test enableExperimentalRollbackInTransaction also for MySQL database?

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/build.yml
  • actions/checkout v4@1d96c772d19495a3b5c517cd2bc0cb401ea0529f
  • actions/setup-node v4
.github/workflows/publish.yml
  • actions/checkout v4@1d96c772d19495a3b5c517cd2bc0cb401ea0529f
  • actions/setup-node v4
npm
package.json
  • @prisma/client 5.12.1
  • husky 9.0.11
  • prettier 3.2.5
  • pretty-quick 4.0.0
  • prisma 5.12.1
  • rimraf 5.0.5
  • typescript 5.4.4
packages/jest-prisma-core/package.json
  • chalk ^4.0.0
  • jest ^28.0.0 || ^29.0.0
  • node >=14.13
packages/jest-prisma-node/package.json
  • jest ^28.0.0 || ^29.0.0
  • jest-environment-node ^28.0.0 || ^29.0.0
  • node >=14.13
packages/jest-prisma/package.json
  • jest ^28.0.0 || ^29.0.0
  • jest-environment-jsdom ^28.0.0 || ^29.0.0
  • node >=14.13

  • Check this box to trigger a request for Renovate to run again on this repository

[Transaction API error]: Transaction already closed: Could not perform operation.

Hello,
I got an error when using jestPrisma.client. Currently I am using jest-prisma version 1.4.0
I try to increased maxWait and timeout to 100000 but the error still occurs.

This is node_modules/@quramy/jest-prisma-core/lib/delegate.js in my project.

image

It seem to timeout param is not include here?
Do you have any suggestions to fix error?

Getting "PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in 'node')" after Upgrading prisma

After upgrading prisma and prisma-client to 5.10.2 and upgrade jest-prisma to 1.8.0 I have been getting

PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in "node"). If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report

Full error:

 ● Test suite failed to run

    PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `node`).
    If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report

      at Object.get (../../../node_modules/.prisma/client/index-browser.js:505:15)
      at PrismaEnvironmentDelegate.teardown (../../../node_modules/@quramy/jest-prisma-core/lib/delegate.js:69:29)
      at PrismaEnvironment.teardown (../../../node_modules/@quramy/jest-prisma/lib/environment.js:22:60)

I think is related to this line:
https://github.com/Quramy/jest-prisma/blob/main/packages/jest-prisma/src/environment.ts#L4

Publication and development of libraries for vitest.

This library has greatly improved our testing experience, thank you.

I am currently working personally on a library that optimizes @quramy/jest-prisma for vitest, would you please allow me to publish it in a new npm under my name?

  • It will be a library that includes jest-prisma-core as a dependency
  • I will include a reference link to this repository, acknowledgements, etc. in the README.

In the meantime, I considered including the vitest version of the library in this repository, but

For these reasons, we think it is better to separate repositories.

Prisma Client Error won't be handled properly

Thank you for the great library.

When I tried to add a following test into the examples/example-prj/src/service, I found a suspicious behavior.

import { Prisma, PrismaClient } from "@prisma/client";

describe.each([
  ["Original PrismaClient", new PrismaClient()],
  ["jestPrisma.client", jestPrisma.client],
])("%s", (_, prisma) => {
  test("handles prisma error properly", async () => {
    try {
      await prisma.user.update({
        data: { name: "" },
        where: { id: "record_not_found" },
      });
    } catch (e) {
      expect(e).toBeInstanceOf(Prisma.PrismaClientKnownRequestError);
    }
  });
});
npm run test:ci src/service/prismaError.test.ts

> [email protected] test:ci
> DATABASE_URL="file:./test.db" jest "src/service/prismaError.test.ts"

 FAIL  src/service/prismaError.test.ts
  Original PrismaClient
    ✓ handles prisma error properly (19 ms)
  jestPrisma.client
    ✕ handles prisma error properly (144 ms)

  ● jestPrisma.client › handles prisma error properly

    expect(received).toBeInstanceOf(expected)

    Expected constructor: PrismaClientKnownRequestError
    Received constructor: PrismaClientKnownRequestError

      12 |       });
      13 |     } catch (e) {
    > 14 |       expect(e).toBeInstanceOf(Prisma.PrismaClientKnownRequestError);
         |                 ^
      15 |     }
      16 |   });
      17 | });

      at Object.<anonymous> (src/service/prismaError.test.ts:14:17)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total
Snapshots:   0 total
Time:        3.502 s

It seems that the prisma error thrown via jestPrisma.client isn't recognized as an instanceof PrismaClientKnownRequestError properly (even though it shows a same name "PrismaClientKnownRequestError")

And this prevents us from handling prisma errors by the way the official document provides.

Do you have any idea on this?
Thanks!

Change base environment from jest-environment-jsdom to jest-environment-node

The following error may occur if @prisma/client runs in jsdom env:

PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `node`).

And the environment inheritance relationship inferred from the package name is misleading. ( e.g. #103 )

Current (jest-prisma v1.x )

  • @quramy/jest-prisma extends jest-enviroment-jsdom
  • @quramy/jest-prisma-node extends jest-environment-node

After (jest-prisma v2.x)

  • @quramy/jest-prisma extends jest-enviroment-node
  • @quramy/jest-prisma-node -> will be removed

Feature Request: Output SQL Queries with Parameters

Thank you for developing this useful tool!

I've been looking for a way to output SQL queries along with their parameters.
Currently, the verboseQuery option does allow for SQL logs to be outputted, but it doesn't include the parameters.

A possible workaround could be the following code snippet, but it tends to be a bit clunky and output some unnecessary logs.

beforeAll(async () => {
   jestPrisma.originalClient.$on("query", (event) => {
      console.debug({ event })
   })
})

prisma/prisma#5026

Since this is a tool for testing, how about adding an option like verboseQueryWithParameters or making it default to output parameters?

If that's acceptable, I will create a pull request.

allow pass `timeout` option for interactive transaction

When I use this library, I got timeout error of prisma transaction.

The error message is as follows.

Transaction API error: Transaction already closed: A query cannot be executed on an expired transaction. The timeout for this transaction was 5000 ms, however 9032 ms passed since the start of the transaction. Consider increasing the interactive transaction timeout or doing less work in the transaction.

I know that taking more than 5 seconds for one test case is not good.
But, I thought it would be more useful to be able to set timeout value.

Is it possible to change the timeout via testEnvironmentOption in the same way as for maxWait?

Here is an example.

// jest.config.js
export default {
  testEnvironmentOptions: {
    maxWait: 10000,
    timeout: 10000,
  },
};

If all is well, I will create a pull request.

$queryRaw doesn't create prepared statements correctly

function getUsers(ids: number[], filter?: string) {
  const filterStatement = `%${filter}%`
  return $queryRaw`SELECT * FROM users WHERE id IN (${Prisma.join([ids])}) ${filter ? Prisma.sql`OR email LIKE ${filterStatement}` : Prisma.empty}`
}

The query above works fine outside of tests but, in a test with jest-prisma It creates a bad prepared-statement that looks like this in the logs

postgres-1  | 2024-03-05 08:10:26.809 UTC [348] ERROR:  syntax error at or near "$2" at character 45
postgres-1  | 2024-03-05 08:10:26.809 UTC [348] STATEMENT:
postgres-1  | 	      SELECT * FROM users WHERE id IN ($1) $2
postgres-1  |

and results in a PrismaClient error

PrismaClientKnownRequestError:
    Invalid `prisma.$queryRaw()` invocation:


    Raw query failed. Code: `42601`. Message: `ERROR: syntax error at or near "$2"`

If Date Object input is specified as DateTime type gte, lte, lt,gt operator in include's where condition doesn't work

I'm sorry if it's already known, but I can't input Date JS object as DateTime type fields at where operators using jestPrisma.client in my environment though original prisma client passes tests.

where: {
                  created_at: {
                    gte: {},
                    ~~~
                    lt: {}
                    ~~~~
                  }
},

The photo shows running first test with prisma-jest, and second with original client.

スクリーンショット 2022-12-10 23 17 17

Version Spec

## node
node: v16.15.0

## DB
Postgresql

## packages
"@quramy/jest-prisma": "^1.3.1",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"ts-loader": "^8.0.4",

## prisma
prisma                  : 4.7.1
@prisma/client          : 4.7.1
Current platform        : darwin
Query Engine (Node-API) : libquery-engine 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/libquery_engine-darwin.dylib.node)
Migration Engine        : migration-engine-cli 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/migration-engine-darwin)
Introspection Engine    : introspection-core 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/introspection-engine-darwin)
Format Binary           : prisma-fmt 272861e07ab64f234d3ffc4094e32bd61775599c (at node_modules/@prisma/engines/prisma-fmt-darwin)
Format Wasm             : @prisma/prisma-fmt-wasm 4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c
Default Engines Hash    : 272861e07ab64f234d3ffc4094e32bd61775599c
Studio                  : 0.477.0

(Addition)
It doesn't reproduce it only just to add created_at Datetime @default(now()) to example-prj' user model. I try to find how to reproduce it asap.

I could reproduce it when condition has include: { where: { [$dateFiledName]: { lt: new Date } } }, so on. Both sqlite and postgresql are reproduced so it may have the problem in js layer .

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["clientExtensions"]
}

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

model User {
  id    String @id
  name  String
  posts Post[]
  createdAt DateTime @default(now())
}

model Post {
  id       String @id
  title    String
  author   User   @relation(fields: [authorId], references: [id])
  authorId String
  createdAt DateTime @default(now())
}
/**
 *
 * @jest-environment-options { "verboseQuery": true }
 *
 */
import { PrismaClient } from "@prisma/client";

describe("Should include date type work around", () => {

  /* NG */
  const prisma = jestPrisma.client;
  /* NG */
  //  const prisma = jestPrisma.originalClient;
  /* OK */
  // const prisma = new PrismaClient();

  beforeEach(async () => {
    await prisma.post.create({
      data: {
        id: "post0",
        title: "post",
        author: {
          create: {
            id: "user0",
            name: "quramy",
          },
        },
      },
    });
  });

  test("include api should work using date type condition", async () => {
    const user = await prisma.user.findFirst({
      where: {
        createdAt: {
          lt: new Date(),
          gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
        },
      },
      include: {
        posts: {
          where: {
            createdAt: {
              lt: new Date(),
              gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
            },
          },
        },
      },
    });

    expect(
      (await prisma.post.findFirst({
        where: {
          author: {
            createdAt: {
              lt: new Date(),
              gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
            },
          },
        },
        include: {
          author: {
            include: {
              posts: {
                where: {
                  createdAt: {
                    lt: new Date(),
                    gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24),
                  },
                },
              },
            },
          },
        },
      }))!.author,
    ).toStrictEqual(user);
  });
});

I confirmed that internal jest-prisma-core originalClient and jest-prisma jestPrisma.originalClient works by force creating and fetch data with overwriting in node_modules folder. In addition, I noticed that validate method in prisma calls with same data, but slightly different though I don't know why. It may be clues.

Original client

スクリーンショット 2022-12-11 13 37 08

Proxy client(both jestPrisma.client and jestPrisma.originalClient)

スクリーンショット 2022-12-11 13 36 57

I also found invalidChildren' value exists in node_modules/@prisma/client/runtime/index.js validate method with jest-prisma proxy though manually PrismaClient in jest test case doesn't.

If an error occurs during transaction, processing cannot continue

When handling a unique constraint error, such as the following, executing a query after a unique constraint error occurs will result in an error.

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

/**
 * Creates a user. Returns true if the creation succeeds or the user already exists.
 */
export async function ensureUser(
  prisma: PrismaClient,
  data: {
    id: number;
    name: string;
  }
): Promise<boolean> {
  try {
    await prisma.user.create({ data });
    return true;
  } catch (err: any) {
    const uniqConstraintFailed = err.code === "P2002";

    if (uniqConstraintFailed) {
      return true;
    } else {
      console.error(err);
      return false;
    }
  }
}
// user.test.ts
test("ensureUser", async () => {
  const data = { id: 1, name: "foo" };

  // succeed
  expect(await ensureUser(jestPrisma.client, data)).toBe(true);

  // succeed
  expect(await ensureUser(jestPrisma.client, data)).toBe(true);

  // fail
  expect(await jestPrisma.client.user.count()).toBe(1);
});
$ jest
 FAIL  ./app.test.ts
  User
    ✕ ensureUser (16 ms)

  ● User › ensureUser

    PrismaClientUnknownRequestError:
    Invalid `jestPrisma.client.user.count()` invocation in
    /path/to/app.test.ts:12:41

       9 const data = { id: 1, name: "foo" };
      10 expect(await ensureUser(jestPrisma.client, data)).toBe(true);
      11 expect(await ensureUser(jestPrisma.client, data)).toBe(true);
    → 12 expect(await jestPrisma.client.user.count(
    Error occurred during query execution:
    ConnectorError(ConnectorError { user_facing_error: None, kind: QueryError(PostgresError { code: "25P02", message: "current transaction is aborted, commands ignored until end of transaction block", severity: "ERROR", detail: None, column: None, hint: None }), transient: false })

I suspect this is due to PostgresSQL's limitation that if an error occurs during a transaction, subsequent queries cannot be issued.

I keep getting "jestPrisma is not defined" errors in e2e testing with nestjs

I'm trying to override the provider (because jest mocking doesn't seem to work) and I'm getting a jestPrisma is not defined error.
I'm doing this

const moduleFixture: TestingModule = await Test.createTestingModule({
  imports: [AppModule],
})
  .overrideProvider(PrismaService)
  .useValue(jestPrisma.client)
  .compile();

I don't know if it's the proper way and I'm confused on how it should be done.

I have most of the configurations in package.json except for the tsconfig.json (which is in the same file). Can anyone provide guidance on testing with nestjs using this lib? I'm trying to test the whole stack by doing a GET request and pulling the data from the seeded data.

Thanks in advance.

Is jest-prisma work with transaction?

Expected

The following tests succeed

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

    throw new Error("Something failed. Affected changes will be rollback.")
  }).catch(console.error)
}

// test code
it("test", async () => {
  const prisma = jestPrisma.client
  
  const before = await prisma.user.aggregate({ _count: true })
  expect(before._count).toBe(0)

  await transaction(prisma)

  const after = await prisma.user.aggregate({ _count: true })
  expect(after._count).toBe(0) // Prisma is expected to rollback, so there should be no increase in users
})

Actual

expect(received).toBe(expected)

Expected: 0
Received: 1

Reproduction

Please check the code I forked.

https://github.com/YutaUra/jest-prisma/blob/reproducing_transaction_bug/examples/example-prj/src/service/Transaction.ts
https://github.com/YutaUra/jest-prisma/blob/reproducing_transaction_bug/examples/example-prj/src/service/Transaction.test.ts

Others

Is the unavailability of Transaction a bug or a specification?

If it is a specification at this time, I think documentation needs to be added. If so, I would be happy to help.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.