Skip to content

Commit

Permalink
Save Tracker instance on client.driver to support concurrent tests, b…
Browse files Browse the repository at this point in the history
…ased on #41
  • Loading branch information
felixmosh committed Mar 5, 2023
1 parent 037ec06 commit 0bf5dd7
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 67 deletions.
50 changes: 25 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,41 +40,41 @@ export async function addUser(user: User): Promise<{ id }> {
import { expect } from '@jest/globals';
import { createTracker, MockClient } from 'knex-mock-client';
import { faker } from '@faker-js/faker';
import { db } from "../common/db-setup";
import { db } from '../common/db-setup';

jest.mock("../common/db-setup", () => {
const knex = require("knex");
return {
db: knex({ client: MockClient }),
};
jest.mock('../common/db-setup', () => {
const knex = require('knex');
return {
db: knex({ client: MockClient }),
};
});

describe('my-cool-controller tests', () => {
let tracker: Tracker;
let tracker: Tracker;

beforeAll(() => {
tracker = createTracker(db);
});

beforeAll(() => {
tracker = createTracker(db);
});
afterEach(() => {
tracker.reset();
});

afterEach(() => {
tracker.reset();
});
it('should add new user', async () => {
const insertId = faker.datatype.number();
tracker.on.insert('users').response([insertId]);

it('should add new user', async () => {
const insertId = faker.datatype.number();
tracker.on.insert('users').response([insertId]);

const newUser = { name: 'foo bar', email: '[email protected]' };
const data = await addUser(newUser);
const newUser = { name: 'foo bar', email: '[email protected]' };
const data = await addUser(newUser);

expect(data.id).toEqual(insertId);
expect(data.id).toEqual(insertId);

const insertHistory = tracker.history.insert;
const insertHistory = tracker.history.insert;

expect(insertHistory).toHaveLength(1);
expect(insertHistory[0].method).toEqual('insert');
expect(insertHistory[0].bindings).toEqual([newUser.name, newUser.email]);
});
expect(insertHistory).toHaveLength(1);
expect(insertHistory[0].method).toEqual('insert');
expect(insertHistory[0].bindings).toEqual([newUser.name, newUser.email]);
});
});
```

Expand Down
22 changes: 14 additions & 8 deletions src/MockClient.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import knex, { Knex } from 'knex';
import { RawQuery } from '../types/mock-client';
import { MockConnection } from './MockConnection';
import { Tracker, TrackerConfig } from './Tracker';
import { Tracker } from './Tracker';

export class MockClient extends knex.Client {
public readonly isMock = true;

constructor(config: Knex.Config & { mockClient: TrackerConfig }) {
constructor(config: Knex.Config) {
super(config);

if (config.dialect) {
Expand All @@ -26,6 +26,12 @@ export class MockClient extends knex.Client {
return response;
}

public setTracker(tracker: Tracker) {
// driver is copied by `makeTxClient` when using a transaction
this.driver = this.driver || {};
this.driver.tracker = tracker;
}

public _query(connection: MockConnection, rawQuery: RawQuery) {
let method: RawQuery['method'] = rawQuery.method;

Expand All @@ -44,15 +50,15 @@ export class MockClient extends knex.Client {
break;
}

// @ts-ignore (since tracker is not on the original interface)
const tracker = this.config.tracker as Tracker;
if (tracker) {
return tracker._handle(connection, { ...rawQuery, method });
const tracker: Tracker = this.driver?.tracker;
if (!tracker) {
throw new Error('Tracker not configured for knex mock client');
}
throw new Error('Tracker not configured for knex mock client');

return tracker._handle(connection, { ...rawQuery, method });
}

private _attachDialectQueryCompiler(config: Knex.Config<any> & { mockClient: TrackerConfig }) {
private _attachDialectQueryCompiler(config: Knex.Config<any>) {
const { resolveClientNameWithAliases } = require('knex/lib/util/helpers');
const { SUPPORTED_CLIENTS } = require('knex/lib/constants');

Expand Down
6 changes: 1 addition & 5 deletions src/Tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { queryMethods, transactionCommands } from './constants';
import { MockConnection } from './MockConnection';
import { isUsingFakeTimers } from './utils';

export type TrackerConfig = Record<string, unknown>;

export type TransactionState = {
id: number;
parent?: number;
Expand Down Expand Up @@ -48,11 +46,9 @@ export class Tracker {
queryMethods.map((method) => [method, this.prepareStatement(method)])
) as Record<QueryMethodType, (rawQueryMatcher: QueryMatcher) => ResponseTypes>;

private readonly config: TrackerConfig;
private responses = new Map<RawQuery['method'], Handler[]>();

constructor(trackerConfig: TrackerConfig) {
this.config = trackerConfig;
constructor() {
this.reset();
}

Expand Down
10 changes: 6 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Knex } from 'knex';
import { Tracker, TrackerConfig } from './Tracker';
import { Tracker } from './Tracker';

export { MockClient } from './MockClient';
export { Tracker } from './Tracker';
export type { RawQuery, QueryMatcher, FunctionQueryMatcher } from '../types/mock-client';

export function createTracker(db: Knex, trackerConfig: TrackerConfig = {}): Tracker {
const tracker = new Tracker(trackerConfig);
db.client.config.tracker = tracker;
export function createTracker(db: Knex): Tracker {
const tracker = new Tracker();

db.client.setTracker(tracker);

return tracker;
}
25 changes: 25 additions & 0 deletions tests/common.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@ import { createTracker, MockClient, Tracker } from '../src';
import { queryMethods } from '../src/constants';

describe('common behaviour', () => {
it('should throw error when querying without tracker initialization', async () => {
const db = knex({
client: MockClient,
});

await expect(db('table_name').where('id', 1)).rejects.toThrowError(
'Tracker not configured for knex mock client'
);
});

it('should support concurrent tests by having different tracker', () => {
const db1 = knex({
client: MockClient,
});

const db2 = knex({
client: MockClient,
});

createTracker(db1);
createTracker(db2);

expect(db1.client.driver.tracker).not.toBe(db2.client.driver.tracker);
});

describe('with db initialized', () => {
let db: Knex;
let tracker: Tracker;
Expand Down
39 changes: 14 additions & 25 deletions tests/transaction.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { faker } from '@faker-js/faker';
import knex from 'knex';
import { MockClient, createTracker } from '../src';
import knex, { Knex } from 'knex';
import { createTracker, MockClient, Tracker } from '../src';

describe('transaction', () => {
it('should support transactions', async () => {
const db = knex({
let db: Knex;
let tracker: Tracker;

beforeAll(() => {
db = knex({
client: MockClient,
});
const tracker = createTracker(db);
tracker = createTracker(db);
});

afterEach(() => {
tracker.reset();
});

it('should support transactions', async () => {
tracker.on.insert('table_name').responseOnce(1);
tracker.on.delete('table_name').responseOnce(1);
tracker.on.select('foo').responseOnce([]);
Expand Down Expand Up @@ -38,11 +47,6 @@ describe('transaction', () => {
});

it('should support transactions with rollback', async () => {
const db = knex({
client: MockClient,
});
const tracker = createTracker(db);

tracker.on.insert('table_name').responseOnce(1);
tracker.on.delete('table_name').responseOnce(1);

Expand Down Expand Up @@ -76,11 +80,6 @@ describe('transaction', () => {
});

it('should support nested transactions', async () => {
const db = knex({
client: MockClient,
});
const tracker = createTracker(db);

tracker.on.insert('table_name').responseOnce(1);
tracker.on.delete('table_name').responseOnce(1);
tracker.on.select('table_name').responseOnce([]);
Expand Down Expand Up @@ -138,11 +137,6 @@ describe('transaction', () => {
});

it('should support transactions with commit', async () => {
const db = knex({
client: MockClient,
});
const tracker = createTracker(db);

tracker.on.insert('table_name').responseOnce(1);
tracker.on.delete('table_name').responseOnce(1);

Expand Down Expand Up @@ -171,11 +165,6 @@ describe('transaction', () => {
});

it('should keep track of interleaving transactions', async () => {
const db = knex({
client: MockClient,
});
const tracker = createTracker(db);

tracker.on.any(/.*/).response(1);

const trx1 = await db.transaction();
Expand Down

0 comments on commit 0bf5dd7

Please sign in to comment.