Skip to content

Commit

Permalink
Commit first version
Browse files Browse the repository at this point in the history
  • Loading branch information
romeerez committed Jun 29, 2022
0 parents commit 080198a
Show file tree
Hide file tree
Showing 11 changed files with 3,678 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DATABASE_URL=postgres://postgres:@localhost:5432/pg-transactional-tests
DATABASE_CAMEL_CASE=true
MIGRATIONS_PATH=tests/migrations
19 changes: 19 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"no-async-promise-executor": "off",
"no-empty": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-this-alias": "off"
}
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea
.env
dist
node_modules
.pnpm-debug.log
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"printWidth": 80,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2
}
142 changes: 142 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# PG Transactional tests

Patches [pg](https://npmjs.com/package/pg) to allow transactional tests.

The purpose of this lib is to make each of your test to run in a separate transaction, rollback after each test, so every change you're making in database disappears.

This allows to focus on testing logic without thinking about clearing database, and this is performed much faster than clearing tables.

`pg` is used by many ORMs, and this test approach worked fine with Sequelize, TypeORM, MikroORM, Objection and Knex.

I have a repo [ORMs overview](https://github.com/romeerez/orms-overview) where I was developing API with all ORMs mentioned above and everything was testing using this approach.

This **does not** work only with Prisma because it's implementation is very different.

## Get started

Install:

```sh
pnpm i -D pg-transactional-tests
```


If you're using Jest, create a script for setup, add it to jest config ("jest" section in package.json):

(if you're using any other test framework than Jest, it should be possible to configure it in similar way)

```js
{
// ...
"jest": {
// ...
"setupFilesAfterEnv": [
"./jest-setup.ts"
]
}
}
```

Write setup code in the script:

```ts
import {
patchPgForTransactions,
startTransaction,
rollbackTransaction,
} from 'pg-transactional-tests';
import { Client } from 'pg';

// construct `pg` client, it's suggested to have a separate database for tests:
export const db = new Client({
connectionString: process.env.DATABASE_URL_TEST,
});

// patch client, this is changing prototype of Client and Pool of `pg`,
// so every instance of `pg` in your app becomes patched
patchPgForTransactions();

// start transaction before each test:
beforeEach(async () => {
await startTransaction(db);
});

// rollback transaction after each test:
afterEach(async () => {
await rollbackTransaction(db);
});
```

## How it works

Every test is wrapped in transaction:

```ts
test('create record', async () => {
await db.query('INSERT INTO sample(...) VALUES (...)')
const sample = await db.query('SELECT * FROM sample WHERE ...')
})
```

This test is producing such SQL:

```sql
BEGIN;
INSERT INTO sample(...) VALUES (...);
SELECT * FROM sample WHERE ...;
ROLLBACK;
```

Under the hood this lib is replacing some of SQL commands:

- `START TRANSACTION` and `BEGIN` command is replaced with `SAVEPOINT "id"`, where id is incremented number
- `COMMIN` becomes `RELEASE SAVEPOINT "id"`
- `ROLLBACK` becomes `ROLLBACK TO SAVEPOINT "id"`

This allows to handle even nested transactions:

```ts
test('nested transactions', async () => {
await db.transaction(async (t) => {
await t.query('INSERT INTO sample(...) VALUES (...)')
})
})
```

Becomes:

```sql
BEGIN;
SAVEPOINT "1";
INSERT INTO sample(...) VALUES (...);
RELEASE SAVEPOINT "1";
ROLLBACK;
```

## Parallel queries

Since every test has own transaction, this library ensures that only 1 connection will be created, because single transaction requires single connection.

This may introduce an unexpected surprise, consider such code:

```ts
await db.transaction(async (transaction) => {
await db.select('SELECT ...')
})
```

Here we started a transaction, but we forgot to use `transaction` variable and used `db` instead to perform a query.

In the first line we started a transaction, which consumes 1 connection, and it will be released only in the end of transaction.

In line 2 we perform a query with `db`, and db client here has to wait for a free connection to execute, but there is only 1 connection which is already taken.

As the result, such code will hang.

But it's not a bad thing, in contrary, when test code hangs this means there was such mistake, and the limitation only helps to find such mistakes.

## Why to choose it over truncating tables?

Transactions are faster than truncating, but we are talking about milliseconds which doesn't really count.

Main benefit is it's simpler to use. With this library you can create persisted seed data, such as record of current user to use across the tests, while if you choose truncating, you'll also need to recreate seed data for each test.
46 changes: 46 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "pg-transactional-tests",
"version": "1.0.0",
"description": "Wraps each test in transaction for `pg` package",
"repository": "https://github.com/romeerez/pg-transactional-tests",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "jest --setupFiles dotenv/config",
"build": "tsc",
"db": "rake-db",
"prepublish": "build"
},
"jest": {
"transform": {
"^.+\\.ts$": "ts-jest"
}
},
"keywords": [
"pg",
"postgres",
"transactional tests",
"test"
],
"author": "Roman Kushyn",
"license": "ISC",
"devDependencies": {
"@types/jest": "^28.1.3",
"@types/pg": "^8.6.5",
"@typescript-eslint/eslint-plugin": "^5.30.0",
"@typescript-eslint/parser": "^5.30.0",
"dotenv": "^16.0.1",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.1.0",
"jest": "^28.1.1",
"prettier": "^2.7.1",
"rake-db": "^1.3.1",
"ts-jest": "^28.0.5",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
},
"peerDependencies": {
"pg": "*"
}
}
Loading

0 comments on commit 080198a

Please sign in to comment.