在对 Knex 进行单元测试时,如何模拟假数据库?

Posted

技术标签:

【中文标题】在对 Knex 进行单元测试时,如何模拟假数据库?【英文标题】:How can I mock a fake database for when unit testing against 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 合作,我遇到了同样的问题。谢谢 【参考方案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。

【讨论】:

以上是关于在对 Knex 进行单元测试时,如何模拟假数据库?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Service 构造函数中对 Controller 进行单元测试和模拟 @InjectModel

SQLiteCantOpenDatabaseException:未知错误(代码 14):无法打开数据库(仅在对应用程序进行单元测试时)

如何在服务层模拟方法

用 Jest 反应单元测试 - 模拟 localStorage 的问题

在对 Spring REST 控制器进行单元测试时注入 @AuthenticationPrincipal

在 Grails 中对服务进行单元测试时如何模拟请求