在对 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 的问题