Reputation: 2921
I'm trying to test a node.js
function which uses knex
.
Instead of just mocking knex
, I think it's interesting to actually run the test on an in-memory database, which makes this test not strictly unitary, but it's, to me, the only useful way to test a repository
class.
It's also the most voted answer here: https://stackoverflow.com/a/32749601/1187067
bookRepo.js
based on knex
bookRepo.test.js
injecting a knex
connection based on SQLite3
.The database is well initialize, the test succeeds and the afterEach()
function is well invoked, but the process never ends which is particularly problematic for pipelines.
The only way I found to stop the process is to call knex.destroy()
in both bookRepo.js and bookRepo.test.js, but it is not possible to destroy knex because it won’t be possible to use it more than once.
Thanks for helping!
bookRepo.js
const knex = require('connection'); // dependency not injected in constructor
const TABLE = 'books';
class BookRepo {
constructor() {
this.knex = knex;
}
static getTable() {
return TABLE;
}
async getTitleById(id) {
const book = await this.knex(TABLE)
.select('title')
.where('id', id)
.first();
return book.title;
}
}
module.exports = BookRepo;
bookRepo.test.js
const { assert } = require('chai');
const mock = require('mock-require');
const {
describe,
it,
before,
after,
beforeEach,
afterEach,
} = require('mocha');
const sqliteConf = {
client: 'sqlite3',
connection: {
filename: ':memory:',
},
useNullAsDefault: true,
};
const knex = require('knex')(sqliteConf);
const booksTable = BookRepo.getTable();
const BOOK_1 = {
id: 1,
title: 'Batman',
};
let bookRepo;
describe('getTitleById', () => {
before(() => {
// To use sqlite3 in the tested module, replace knexfile (required by connections)
mock('../knexfile', {
development: sqliteConf,
});
// as knex is not injected in constructor, we need to require BookRepo after having mocked knexfile.
const BookRepo = require('../bookRepo');
bookRepo = new BookRepo();
});
after(() => {
mock.stopAll();
knex.destroy(); // destroys only the connection of the test, not in bookRepo
});
/**
* We initialize the SQLite database before each test (create table and insert)
*/
beforeEach(async () => {
// drop table
await knex.schema.dropTableIfExists(booksTable);
// create table
await knex.schema.createTable(booksTable, (table) => {
table.integer('id');
table.string('title');
});
// Insertion
await knex.transaction((t) => knex(booksTable)
.transacting(t)
.insert(BOOK_1)
.then(t.commit)
.catch(t.rollback))
.catch((e) => {
console.error(e);
throw new Error('failed to insert test data');
});
});
/**
* We drop the SQLite table after each test
*/
afterEach(async () => {
await knex.schema.dropTableIfExists(booksTable); // table well dropped
});
it('returns the title of the given book', async () => {
const bookRepo = new BookRepo();
const expectedTitle = BOOK_1.title;
const retrievedTitle = await bookRepo.getTitleById(BOOK_1.id);
assert.equal(retrievedTitle, expectedTitle); // OK
});
});
package.json
…
dependencies": {
"knex": "^0.20.1",
},
"devDependencies": {
"chai": "latest",
"mocha": "^6.2.2",
"sqlite3": "latest",
}
}
Upvotes: 1
Views: 1413
Reputation: 7664
Since you're using mocha, it seems best to use Mocha's after
hook and call destroy
from there. Likewise, you could instantiate Knex in before
. I can't think of a reason you would need to call destroy
in your non-test code.
Upvotes: 1