【问题标题】:How can I mock a fake database for when unit testing against Knex?在对 Knex 进行单元测试时,如何模拟假数据库?
【发布时间】:2015-03-25 17:57:09
【问题描述】:

我一直在使用Knex 成功连接到后端数据库。但我希望能够对我的代码进行单元测试。有没有办法模拟数据库连接?

我尝试过使用proxyquire,但似乎无法正常工作。

问题似乎出在 Knex 的初始化方式上。

var knex = require('knex')({
  client: 'mysql',
  connection: {}
});

我设置 knex 以在我的单元测试中进行模拟。

myService = proxyquire('../app/myService', {
        'knex': knexProxy
});

我的服务包括 knex。

var knex = require('knex').knex,

当我的服务运行查询时,它会失败。

var sql = knex("table_name");
sql.insert(rowToInsert, "auto_increment_id");
sql.then(function (insertId) {
    resolve();
}, function (err) {
    reject(err);
});

由于某种原因,我似乎无法在它尝试连接之前捕获请求。

我也尝试过创建custom Knex Client,但也没有成功。

【问题讨论】:

  • 你找到解决办法了吗?我正在与 Knex 合作,我遇到了同样的问题。谢谢

标签: javascript node.js unit-testing knex.js proxyquire


【解决方案1】:

这对我有用,希望对某人有所帮助:

//db.ts
import knex from 'knex';

const db = knex({
  client: 'pg',
  connection: {},
  pool: { min: 0, max: 1 }
});

export default db('someTableName');

//myFunction.ts
//somewhere inside a function
const data = await db
    // ?  Knex query builders are mutable so when re-using them .clone() is necessary.
    .clone()
    .where({ pk: 'someId', sk: 'someId2' })
    .select('data')
    .orderBy('inserted_at', 'desc')
    .first()

//myFunction.test.ts
describe("myFunction", () => {
  beforeEach(() => {
    jest.spyOn(db, "clone").mockImplementation(() => db);
    jest.spyOn(db, "select");
    jest.spyOn(db, "where");
    jest.spyOn(db, "orderBy");
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it("should work as expected", async () => {
    jest.spyOn(db, "first").mockResolvedValueOnce("desiredReturnValue");

    await myFunction();

    expect(db.where).toHaveBeenCalledWith({
      pk: "someId",
      sk: "someId2",
    });
    expect(db.select).toHaveBeenCalledWith("data");
    expect(db.orderBy).toHaveBeenCalledWith("inserted_at", "desc");
    expect(db.first).toHaveBeenCalledTimes(1);
  });
});

【讨论】:

  • 你能在答案中解释你的解决方案吗?
【解决方案2】:

我编写了一个名为 knex-mock-client 的小型库,它正是这样做的,它允许您使用 mockClient 设置您的数据库“连接”,该模拟客户端将跟踪您的调用并帮助您做出响应。

例如:

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

【讨论】:

    【解决方案3】:

    我正在使用jest,你可以这样做:

       jest.mock('knex', () => {
            const fn = () => {
                return {
                    select: jest.fn().mockReturnThis(),
                    from: jest.fn().mockReturnThis(),
                    where: jest.fn().mockReturnThis(),
                    first: jest.fn().mockReturnThis(),
                    insert: jest.fn().mockReturnThis(),
                    raw: jest.fn().mockReturnThis(),
                    then: jest.fn(function (done) {
                      done(null)
                    })
                    
                }
            }
            return fn
        })
    

    【讨论】:

      【解决方案4】:

      使用jest

      在您的应用根目录中创建文件/__mocks__/knex.js

      module.exports = () => ({
        select: jest.fn().mockReturnThis(),
        from: jest.fn().mockReturnThis(),
        where: jest.fn().mockReturnThis(),
        first: jest.fn().mockReturnThis(),
        then: jest.fn(function (done) {
          done(null)
        })
      })
      

      将所需的返回值传递给done

      【讨论】:

      • 我跟不上。你能添加一个使用这个模拟的例子吗?
      【解决方案5】:

      我用 jest 来模拟 knex,但我必须定义一个包含我使用的方法的对象。 不是最优雅的解决方案,但正在工作

      let knexMock = () => {
          const fn = () => {
              return {
                  returning: function() {
                      return {
                          insert: jest.fn().mockImplementation(() => [123123])
                      }
                  },
                  insert: jest.fn()
              }
          }
          fn.raw = jest.fn()
          return fn
      }
      
      knex.mockImplementation(knexMock)
      

      【讨论】:

      • 在摸索了 sinon 和 proxyquire 之后,这对我有用。谢谢。
      【解决方案6】:

      我一直在使用in-memory Sqlite3 databases 进行自动化测试,并取得了巨大成功。这不是真正的单元测试,但它的运行速度确实比 MySQL 或 PostgreSQL 快得多。我已经发布了有关此解决方案的更多详细信息on a different question

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-03-28
        • 2013-10-08
        • 2019-08-22
        • 2020-01-03
        • 1970-01-01
        • 2019-03-19
        • 1970-01-01
        相关资源
        最近更新 更多