如何使用 jest 模拟 Knex

How to mock Knex using jest

我正在尝试使用 jest 模拟 knex 以实现以下实现

 const knex = Knex({ client: "mysql"})

    const query = knex("table_name")
      .where({
        title: "xxx-yyy",
        lang: "eng"
      })
      .select()
      .orderBy('date', 'desc')
      .toSQL()
      .toNative()
      

下面我试过了,但没有用,出现错误“TypeError: knex is not a function”

jest.mock("knex", () => {
  return () => {
    return {
      knex: () => {
        where: () => {
          select: () => {
            orderBy: () => {
              toSQL: () => {
                toNative: jest.fn()
              }
            }
          }
        }
      }
    }
  }
})

非常感谢任何帮助。

jest.mock() 将模拟具有自动模拟版本的模块,factoryoptions 是可选的。

您可以使用 mockFn.mockReturnThis() 模拟方法链调用。

另外,如果你在模块作用域内初始化Knex,你需要require在设置mock之后模块。

例如

index.js:

import Knex from 'knex';

const knex = Knex({ client: 'mysql' });

export function main() {
  const query = knex('table_name')
    .where({
      title: 'xxx-yyy',
      lang: 'eng',
    })
    .select()
    .orderBy('date', 'desc')
    .toSQL()
    .toNative();
}

index.test.js:

import Knex from 'knex';

jest.mock('knex');

describe('68717941', () => {
  test('should pass', () => {
    const querybuilder = {
      where: jest.fn().mockReturnThis(),
      select: jest.fn().mockReturnThis(),
      orderBy: jest.fn().mockReturnThis(),
      toSQL: jest.fn().mockReturnThis(),
      toNative: jest.fn(),
    };
    const mKnex = jest.fn().mockReturnValue(querybuilder);
    Knex.mockReturnValue(mKnex);
    const { main } = require('./');
    main();
    expect(Knex).toBeCalledWith({ client: 'mysql' });
    expect(mKnex).toBeCalledWith('table_name');
    expect(querybuilder.where).toBeCalledWith({ title: 'xxx-yyy', lang: 'eng' });
    expect(querybuilder.select).toBeCalledTimes(1);
    expect(querybuilder.orderBy).toBeCalledWith('date', 'desc');
    expect(querybuilder.toSQL).toBeCalledTimes(1);
    expect(querybuilder.toNative).toBeCalledTimes(1);
  });
});

测试结果:

 PASS  examples/68717941/index.test.js (8.844 s)
  68717941
    ✓ should pass (7464 ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.js |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        9.439 s

检查我写的一个小库 knex-mock-client,它允许你编写一个单元测试,使用 DB 而不是真正的 DB :]。

这种方法的好处是,您不需要了解所有 Knex's 内部信息(这可能会随着时间的推移而改变),并且它允许您控制“数据库”应该 return 的数据针对特定查询。

用法示例:

// my-cool-controller.ts
import { db } from '../common/db-setup';

export async function addUser(user: User): Promise<{ id }> {
  const [insertId] = await db.insert(user).into('users');

  return { id: insertId };
}
// my-cool-controller.spec.ts
import { expect } from '@jest/globals';
import knex, { Knex } from 'knex';
import { getTracker, MockClient } from 'knex-mock-client';
import faker from 'faker';

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

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

  beforeAll(() => {
    tracker = getTracker();
  });

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

  it('should add new user', async () => {
    const insertId = faker.datatype.number();
    tracker.on.insert('users').response([insertId]);
    
    const newUser = { name: 'foo bar', email: 'test@test.com' };
    const data = await addUser(newUser);

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

    const insertHistory = tracker.history.insert;

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