I'm using PG library to connecting to Postgres DB,
Now, I want to write unit tests for database access and I don't know how I can do that. Actually, I need some mock Postgres server or something to mock Postgres.
I'm using Mocha for testing
below is one of my classes to access database
import { Pool } from "pg";
export class DbAccess implements IdbAccess {
private static readonly postgres = new Pool();
constructor(#inject(TYPES.ILogger) private readonly logger: ILogger) {}
public saveConsumer(consumer: IAPIGWConsumer): Promise<IAPIGWConsumer> {
return this.queryOne`
INSERT INTO users (consumer_id, email)
VALUES (${consumer.consumer_id}, ${consumer.email})
ON CONFLICT (consumer_id) DO UPDATE SET email = ${consumer.email}
RETURNING *
`;
}
}
I would appreciate any help, thank you.
If you want to mock or stub some package/method/module, you need to install a mock/stub library such as sinon.js, jest.js to achieve this.
Here is the unit test solution using sinon.js. For simplicity and clarity, I've removed unnecessary parts, such as DI.
db.ts:
import { Pool } from "pg";
export class DbAccess {
private static readonly postgres = new Pool();
public saveConsumer(consumer) {
return DbAccess.postgres.query(`
INSERT INTO users (consumer_id, email)
VALUES (${consumer.consumer_id}, ${consumer.email})
ON CONFLICT (consumer_id) DO UPDATE SET email = ${consumer.email}
RETURNING *
`);
}
}
db.test.ts:
import pg from "pg";
import sinon from "sinon";
import { expect } from "chai";
describe("59624370", () => {
afterEach(() => {
sinon.restore();
});
it("should pass", async () => {
const mPool = { query: sinon.stub().resolves({ rows: [] }) };
const poolStub = sinon.stub(pg, "Pool").callsFake(() => mPool);
const { DbAccess } = require("./db");
const db = new DbAccess();
const consumer = { consumer_id: 1, email: "example#gmail.com" };
const actual = await db.saveConsumer(consumer);
expect(actual).to.be.eql({ rows: [] });
sinon.assert.calledOnce(poolStub);
sinon.assert.calledOnce(mPool.query);
});
});
Unit test results with coverage report:
59624370
✓ should pass (132ms)
1 passing (141ms)
------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
db.test.ts | 100 | 100 | 100 | 100 | |
db.ts | 100 | 100 | 100 | 100 | |
------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59624370
Related
Minimal, reproducible example
SomeClass.js
import Rx from "rxjs/Rx.js";
class SomeClass {
constructor() {
this.subject = new Rx.Subject();
}
}
SomeClass.test.js
import SomeClass from "./SomeClass.js";
let someClass = new SomeClass();
After running npm test, I get an error saying TypeError: Cannot read property 'Subject' of undefined. Rxjs has been installed in my project.
Any ideas?
Option 1. use Manual Mocks, mock rxjs/Rx.js module explicitly.
E.g.
SomeClass.js:
import Rx from 'rxjs/Rx.js';
export default class SomeClass {
constructor() {
this.subject = new Rx.Subject();
}
}
SomeClass.test.js:
import SomeClass from './SomeClass';
import Rx from 'rxjs/Rx.js';
jest.mock('rxjs/Rx.js', () => {
return { Subject: jest.fn() };
});
describe('65261235', () => {
it('should pass', () => {
let someClass = new SomeClass();
expect(Rx.Subject).toBeCalledTimes(1);
});
});
unit test result:
PASS examples/65261235/SomeClass.test.js
65261235
✓ should pass (2 ms)
--------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
SomeClass.js | 100 | 100 | 100 | 100 |
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.877 s
Option 2. enable automock for jestjs.
This option tells Jest that all imported modules in your tests should be mocked automatically. All modules used in your tests will have a replacement implementation, keeping the API surface.
Considering the example below
//AuthenticatorService.js
const UserService = require("./UserService");
class AuthenticatorService{
constructor(){
//Some initialisation
}
//Some methods
}
module.exports = new AuthenticatorService();
//UserService.js
class UserService{
constructor(){
//Again some initialisations
}
}
module.exports = new UserService();
So when I try to mock the UserService class in my AuthenticatorService.spec.js file the constructor of the UserService is getting executed as it is being exported in that way. But I don't want that to be executed. Is it possible to mock the UserService module inside AuthenticatorService.spec.js file without invoking its constructor.
You could use jest.mock(moduleName, factory, options) to do this.
E.g.
UserService.js:
class UserService {
constructor() {
console.log('initialize UserService');
}
hello() {
console.log('user hello real implementation');
}
}
module.exports = new UserService();
AuthenticatorService.js:
const userService = require('./UserService');
class AuthenticatorService {
constructor() {
console.log('initialize AuthenticatorService');
}
hello() {
userService.hello();
}
}
module.exports = new AuthenticatorService();
AuthenticatorService.test.js:
const authenticatorService = require('./AuthenticatorService');
const userService = require('./UserService');
jest.mock('./UserService', () => {
return { hello: jest.fn() };
});
describe('62967707', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', () => {
userService.hello.mockImplementationOnce(() => console.log('user hello mocked implementation'));
authenticatorService.hello();
expect(userService.hello).toBeCalledTimes(1);
});
});
unit test result:
PASS stackoverflow/62967707/AuthenticatorService.test.js (12.636s)
62967707
✓ should pass (11ms)
console.log
initialize AuthenticatorService
at new AuthenticatorService (stackoverflow/62967707/AuthenticatorService.js:5:13)
console.log
user hello mocked implementation
at Object.<anonymous> (stackoverflow/62967707/AuthenticatorService.test.js:13:60)
-------------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
AuthenticatorService.js | 100 | 100 | 100 | 100 |
-------------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 14.703s
As you can see in the logs, the real constructor of UserService will not execute. And we mocked hello method of the UserService class's instance.
I have a function that looks roughly like this:
import {someLibraryMethod} from 'someLibrary';
function setFullWidth(el) {
someLibraryMethod(el);
el.setAttribute('width', '100%');
}
I'm testing this function by using Sinon to create a mock element that only supports setAttribute:
const fakeElement = {
setAttribute: sinon.spy()
};
setFullWidth(fakeElement);
assert(fakeElement.setAttribute.calledWithExactly('width', '100%'));
The problem is that someLibraryMethod assumets el is an HTML element and tries to call some other methods on it, such as hasAttribute. I could add these as stubs or spies to fakeElement too, but I was wondering if Sinon has a way to create an object that will spy on any method or property access on it?
Something like:
const fakeElement = sinon.spyOnAnyPropety();
fakeElement.setAttribute(); // valid
fakeElement.hasAttribute(); // valid
fakeElement.foo(); // also valid
// some way to check which methods were called
You can use sinon.stub(obj) to
Stubs all the object’s methods.
E.g.
index.ts:
export function setFullWidth(el) {
el.setAttribute('width', '100%');
el.hasAttribute('width');
el.foo();
}
index.spec.ts:
import { setFullWidth } from './';
import sinon, { SinonStubbedInstance } from 'sinon';
import { expect } from 'chai';
describe('58868361', () => {
const fakeElement = {
setAttribute(attr, value) {
console.log('setAttribute');
},
hasAttribute(attr) {
console.log('hasAttribute');
},
foo() {
console.log('foo');
},
};
afterEach(() => {
sinon.restore();
});
it('should spy all methods of el', () => {
const logSpy = sinon.spy(console, 'log');
const stub: SinonStubbedInstance<typeof fakeElement> = sinon.stub(fakeElement);
setFullWidth(fakeElement);
expect(stub.setAttribute.calledWithExactly('width', '100%')).to.be.true;
expect(stub.hasAttribute.calledWithExactly('width')).to.be.true;
expect(stub.foo.calledOnce).to.be.true;
expect(logSpy.callCount).to.be.equal(0);
logSpy.restore();
});
it('should restore to original methods of el', () => {
const logSpy = sinon.spy(console, 'log');
setFullWidth(fakeElement);
expect(logSpy.callCount).to.be.equal(3);
});
});
Unit test result with 100% coverage:
58868361
✓ should spy all methods of el
setAttribute
hasAttribute
foo
✓ should restore to original methods of el
2 passing (13ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mongoose5.x-lab/tree/master/src/stackoverflow/58868361
I'm creating a jQuery object inside a function that I'm testing. I need to mock the prop method call.
How can I do this? I'm using Jest. I tried with Sinon as well, but I couldn't get this working.
Here's my method:
import $ from 'jquery';
export function myFunc(element) {
var htmlControl = $(element);
var tagName = htmlControl.prop('tagName');
}
Here is the unit test solution using sinonjs:
index.ts:
import $ from 'jquery';
export function myFunc(element) {
var htmlControl = $(element);
var tagName = htmlControl.prop('tagName');
}
index.spec.ts:
import proxyquire from 'proxyquire';
import sinon from 'sinon';
import { expect } from 'chai';
describe('myFunc', () => {
it('should mock prop() method', () => {
const el = {};
const propStub = sinon.stub().returnsThis();
const jqueryStub = sinon.stub().callsFake(() => {
return {
prop: propStub
};
});
const { myFunc } = proxyquire('./', {
jquery: jqueryStub
});
myFunc(el);
expect(jqueryStub.calledWith(el)).to.be.true;
expect(propStub.calledWith('tagName')).to.be.true;
});
});
Unit test result with 100% coverage:
myFunc
✓ should mock prop() method (156ms)
1 passing (161ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57461604
I try to figure out how to test this kind of method
// Let's say models === null when we instantiate
public initialize(mongodb: MongoDb): this {
if (!this.models) {
this.models = {
users: new models.UserModel(mongodb),
};
}
return this;
}
public getModels(): Models | null {
return this.models || null;
}
My coverage is still staying that I didn't test the if part... This is not really true because when I ask for the getModels I can test its value (so implicitly the if.
Any idea?
Here is the solution:
index.ts:
import * as models from './models';
import { MongoDb, Models } from './interfaces';
export class UserDataSource {
private models: Models | null = null;
public initialize(mongodb: MongoDb): this {
if (!this.models) {
this.models = {
users: new models.UserModel(mongodb)
};
}
return this;
}
public getModels(): Models | null {
return this.models || null;
}
}
Unit test, index.spec.ts:
import { UserDataSource } from './';
import { MongoDb } from './interfaces';
import * as models from './models';
describe('UserDataSource', () => {
const mockedMongodb: MongoDb = {};
describe('#initialize', () => {
it('should initlialize models correctly', () => {
const userDataSource = new UserDataSource();
const actualValue = userDataSource.initialize(mockedMongodb);
expect(userDataSource.getModels()).toEqual(expect.objectContaining({ users: expect.any(models.UserModel) }));
expect(actualValue).toBe(userDataSource);
});
it('should not initialize models', () => {
const userDataSource = new UserDataSource();
// tslint:disable-next-line: no-string-literal
userDataSource['models'] = [];
const actualValue = userDataSource.initialize(mockedMongodb);
expect(actualValue).toBe(userDataSource);
});
});
describe('#getModels', () => {
it('should get models correctly', () => {
const userDataSource = new UserDataSource();
const actualValue = userDataSource.getModels();
expect(actualValue).toEqual(null);
});
it('should get models correctly and not null', () => {
const userDataSource = new UserDataSource();
// tslint:disable-next-line: no-string-literal
userDataSource['models'] = [];
const actualValue = userDataSource.getModels();
expect(actualValue).toEqual([]);
});
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/52729002/index.spec.ts
UserDataSource
#initialize
✓ should initlialize models correctly (5ms)
✓ should not initialize models (7ms)
#getModels
✓ should get models correctly (1ms)
✓ should get models correctly and not null (1ms)
-----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-----------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
52729002 | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
52729002/models | 100 | 100 | 100 | 100 | |
UserModel.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
-----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 6.157s
Here is the completed demo: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/52729002