I' struggling mocking my sqlite3 implementation for my jest tests. I prepared a Stackblitz with my problem, that I unfortunately do not get to run jest with :(
I want to write tests for the attach method and basically just check whether db.prepare
and db.prepare.run
have been called with correct arguments:
async attach(id: string): Promise<void> {
await this.dbService.query((cb) => this.dbService.db.prepare(
"UPDATE table SET boolean = true WHERE id = ?",
id, cb).run()
);
}
sqlite3 implementation in my db service:
import sqlite3 from "sqlite3";
export class DBService {
db: sqlite3.Database;
private static instance: DBService | undefined = undefined;
constructor() {
this.db = new sqlite3.Database(`${someFolder}/somedb.db`);
}
public static getInstance(): DBService {
if (!this.instance) {
this.instance = new DBService();
}
return this.instance;
}
async query<T>(query: (cb: (error: Error | null, result: T | null) => void) => void): Promise<T> {
return new Promise((resolve, reject) => {
this.db.serialize(() => {
query((err: Error | null, res: T | null) => {
if (err) {
console.log("query error: ", err);
reject(err);
} else {
resolve(res as T);
}
});
});
});
}
}
test fails with not finding the mocked prepare
:
jest.mock("sqlite3");
jest.mock("./services/database/DBService.ts", () => {
const mockInstance = {
query: jest.fn(),
db: {
prepare: jest.fn()
}
};
return {
DBService: {
getInstance: jest.fn(() => mockInstance),
}
};
});
beforeEach(() => {
mockQuery = DBService.getInstance().query as jest.Mock;
const dbMock = {
prepare: jest.fn(() => ({
run: jest.fn(),
})),
};
// Mock sqlite3.Database constructor
(sqlite3.Database as jest.Mock).mockImplementation(() => dbMock);
service = SyncService.getInstance();
});
await service.attach(1);
expect(mockQuery).toHaveBeenCalledTimes(1);
expect(dbMock.prepare).toHaveBeenCalledWith(
"UPDATE table SET boolean = true WHERE id = ?",
id,
expect.any(Function)
);
expect(runMock).toHaveBeenCalledTimes(1);
Your code have some issues:
Jest support TypeScript via babel
, ts-jest
, see Using TypeScript, but I don't see the setup in your code.
You don't need to mock sqlite3
module. You are testing the servicea.ts
file, and it only depends on dbservice.ts
module. So mock dbservice.ts
module is enough. So you can delete __mocks__
folder.
Mix callbacks and async/await
in JS is not recommended. See Can I mix callbacks and async/await patterns in NodeJS?
You should mock the DBService
's .query()
method and call the query
callback and its callback, so that await service.attach(1)
statement will wait for all asynchronous code to finish executing.
The moduleName
argument of jest.mock()
should be ../dbservice
, not ../../dbservice
__tests__/servicea.ts
:
import { ServiceA } from '../servicea';
import { DBService } from '../dbservice';
jest.mock('../dbservice', () => {
const queryCallback = jest.fn();
const mockInstance = {
initDatabase: jest.fn(),
query: jest.fn().mockImplementation((query) => query(queryCallback)),
db: {
prepare: jest.fn().mockReturnThis(),
run: jest.fn(),
},
};
return {
DBService: {
getInstance: jest.fn(() => mockInstance),
},
};
});
describe('DB Service Tests', () => {
const id = 1;
let service: ServiceA;
let mockQuery: jest.Mock;
let mockPrepare: jest.Mock;
beforeEach(() => {
mockQuery = DBService.getInstance().query as jest.Mock;
mockPrepare = DBService.getInstance().db.prepare as jest.Mock;
service = ServiceA.getInstance();
});
afterEach(() => {
jest.clearAllMocks();
});
it('should call database instance correctly', async () => {
await service.attach(1);
expect(mockQuery).toHaveBeenCalledTimes(1);
expect(mockPrepare).toHaveBeenCalledWith(
'UPDATE tables SET boolean = true WHERE id = ?',
id,
expect.any(Function)
);
});
});
Test result:
PASS __tests__/servicea.test.ts
DB Service Tests
✓ should call database instance correctly (5 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.755 s, estimated 5 s
Ran all test suites.
Live demo: stackblitz