I am trying to unit test the below listEntities function by mocking runQuery and createQuery functions. Maybe I should just give up and do an integration test with an emulator. Anyway, here is my code
Implementation:
const Datastore = require('#google-cloud/datastore');
const datastore = Datastore();
const runQueryDS = (query) => datastore.runQuery(query);
const createQueryDS = (kind) => datastore.createQuery(kind);
export const listEntities = (kind, runQuery = runQueryDS, createQuery = createQueryDS) => {
console.log('listEntities');
const query = createQuery(kind);
runQuery(query).then((results) => results[0]);
};
Test:
import { listEntities } from './datastore.api';
describe('datastore api', () => {
describe('listEntities', () => {
test('should return list of items', () => {
console.log('begin test');
const kind = 'TestRun';
const createdQuery = 'createdQuery';
const expectedResult = ['returnedFromQuery'];
const returnedFromExecutedQuery = [expectedResult];
const createQuery = jest.fn().mockImplementation(() => (createdQuery));
const runQuery = jest.fn().mockImplementation(() => (returnedFromExecutedQuery));
const result = listEntities(kind, runQuery, createQuery);
expect(result).toEqual(expectedResult);
});
});
});
This is the error I get
FAIL app/datastore.api.test.js
● Test suite failed to run
Cannot find module './datastore_client_config' from 'datastore_client.js'
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:191:17)
at Object.<anonymous> (node_modules/#google-cloud/datastore/src/v1/datastore_client.js:30:18)
Thank you!
Unit testing should be completed first, followed by integration testing. Unit tests are easier to write than integration tests, and have good isolation, do not rely on external services, have no side effects, and can run in different environments.
Here is the unit test solution:
index.js:
const Datastore = require('#google-cloud/datastore');
const datastore = Datastore();
const runQueryDS = (query) => datastore.runQuery(query);
const createQueryDS = (kind) => datastore.createQuery(kind);
const listEntities = (kind, runQuery = runQueryDS, createQuery = createQueryDS) => {
console.log('listEntities');
const query = createQuery(kind);
return runQuery(query).then((results) => results[0]);
};
module.exports = { listEntities };
index.test.js:
const { listEntities } = require('./');
const Datastore = require('#google-cloud/datastore');
jest.mock('#google-cloud/datastore', () => {
const mDatasotre = {
runQuery: jest.fn(),
createQuery: jest.fn(),
};
return jest.fn(() => mDatasotre);
});
describe('47128513', () => {
describe('#listEntities', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should list entities', async () => {
const mDatastore = Datastore();
mDatastore.createQuery.mockReturnValueOnce('fake query');
mDatastore.runQuery.mockResolvedValueOnce([{ id: 1 }]);
const actual = await listEntities('kind');
expect(actual).toEqual({ id: 1 });
expect(mDatastore.createQuery).toBeCalledWith('kind');
expect(mDatastore.runQuery).toBeCalledWith('fake query');
});
});
});
unit test result with coverage report:
PASS src/stackoverflow/47128513/index.test.js (12.111s)
47128513
#listEntities
✓ should list entities (12ms)
console.log src/stackoverflow/47128513/index.js:355
listEntities
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.865s, estimated 15s
source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/47128513
Related
I am new to jest, sorry if this is a trivial question but I went through the official jest docs and I am not able to find a solution to the problem.
I am developing a very simple app in nodejs that consumes data from a websocket and propagates it downstream to a set of consumers via zeromq.
The code is the following:
app.js:
const initializer = require("./dependencyInitializer");
const sock = initializer.zmqSock();
const ws = initializer.wsClient();
ws.on('update', data => {
sock.send([data.topic, JSON.stringify(data)]);
});
The websocket client is a class from a third party library extending from EventEmitter.
I would like to create a test asserting that the sock.send function is called exactly once inside the handler of the 'update' event.
This is my approach:
app.spec.js:
const ws = require("./app");
const initializer = require("./dependencyInitializer");
jest.mock("./dependencyInitializer", () => {
return {
wsClient: jest.fn(() => {
const EventEmitter = require("events")
const emitter = new EventEmitter()
return emitter;
}),
zmqSock: jest.fn(() => {
return {
send: jest.fn()
}
})
}
});
describe('on message received from websocket',() => {
it('should pass it to zmq', () => {
const data = {result: "ok"};
expect(initializer.wsClient).toHaveBeenCalledTimes(1);
expect(initializer.zmqSock).toHaveBeenCalledTimes(1);
const _sock = initializer.zmqSock();
const _ws = initializer.wsClient();
_ws.emit("update", data);
expect(_sock.send).toHaveBeenCalledTimes(1);
});
});
The test fails with the following:
on message received from websocket › should pass it to zmq
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
28 | const _ws = initializer.wsClient();
29 | _ws.emit("update", data);
> 30 | expect(_sock.send).toHaveBeenCalledTimes(1);
| ^
31 | });
32 | });
I am not sure if I am on the right path, I would like to understand what would be the best approach to develop a test like this.
Thanks
After mocking by jest.mock(), when the .wsClient() and .zmqSock() methods are called in the app.js file and app.spec.js file, the sock and ws objects in app.js are different with the app.spec.js.
{
wsClient: jest.fn(() => {
const EventEmitter = require("events")
const emitter = new EventEmitter()
return emitter;
})
}
Every time you call .wsClient(), it will create a new object.
Emitter can only listen for events from its own emit. The solution is to create the mock emitter and sock objects in the mock factory.
app.js:
const initializer = require('./dependencyInitializer');
const sock = initializer.zmqSock();
const ws = initializer.wsClient();
ws.on('update', (data) => {
sock.send([data.topic, JSON.stringify(data)]);
});
module.exports = { sock, ws };
app.test.js:
const app = require('./app');
const initializer = require('./dependencyInitializer');
jest.mock(
'./dependencyInitializer',
() => {
const EventEmitter = require('events');
const emitter = new EventEmitter();
const mSock = { send: jest.fn() };
return {
wsClient: jest.fn(() => emitter),
zmqSock: jest.fn(() => mSock),
};
},
{ virtual: true }
);
describe('on message received from websocket', () => {
it('should pass it to zmq', () => {
const data = { result: 'ok' };
expect(initializer.wsClient).toHaveBeenCalledTimes(1);
expect(initializer.zmqSock).toHaveBeenCalledTimes(1);
const _sock = initializer.zmqSock();
const _ws = initializer.wsClient();
// check if they have same reference
expect(app.sock).toBe(_sock);
expect(app.ws).toBe(_ws);
_ws.emit('update', data);
expect(_sock.send).toHaveBeenCalledTimes(1);
});
});
test result:
PASS examples/70024105/app.test.js (9.279 s)
on message received from websocket
✓ should pass it to zmq (2 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
app.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.112 s
I'm new to writing mocha unit tests and would like to ask how the unit test for the following code would look like (especially for the BigQuery part). The code is in a PubSub triggered Cloud Function and inserts rows into a BigQuery table:
/**
* Triggered from a message on a Cloud Pub/Sub topic.
*
* #param {!Object} event Event payload.
* #param {!Object} context Metadata for the event.
*/
const {BigQuery} = require('#google-cloud/bigquery');
const bigqueryClient = new BigQuery();
const dataset = 'dataset_name';
const table = 'table_name';
exports.sendtobigquery = (event, context) => {
const pubsubMessage = Buffer.from(event.data, 'base64').toString();
BigQueryInsert(pubsubMessage, dataset, table);
};
async function BigQueryInsert(pubsubMessage, dataset, table) {
const date = new Date().toISOString(); // new date
const rows = [{field1: pubsubMessage, field2: date}]; // new field2
await bigqueryClient
.dataset(dataset)
.table(table)
.insert(rows);
}
I've seen here Mocking ES6 BigQuery class that sinon and proxyquire should be used but I don't understand how to do that for my code exactly.
Any help is appreciated.
Update:
I added a new date field as part of the row but I'm having trouble here too when it comes to the unit test. For the date I tried using sinon.useFakeTimers in the index.test.js from the answer like that
describe('66267929', () => {
const now = new Date();
const date = sinon.useFakeTimers(now.getTime());
beforeEach(() => {
sinon.restore();
});
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
... // same code
... // same code
sinon.assert.calledWithExactly(bigqueryClientStub.insert, [{ field1: 'teresa teng', field2: date }]);
});
});
but that resulted in "AssertError: expected stub to be called once but was called 0 times". How can this be done?
Since you call the BigQueryInsert function without async/await, we need to flush the promise queue to ensure that all asynchronous method calls have been completed on the bigqueryClient object.
We use proxyquire and sinonjs to stub the BigQuery constructor.
We use returnsThis() to achieve the chain methods call.
E.g.
index.js:
const { BigQuery } = require('#google-cloud/bigquery');
const bigqueryClient = new BigQuery();
const dataset = 'dataset_name';
const table = 'table_name';
exports.sendtobigquery = (event, context) => {
const pubsubMessage = Buffer.from(event.data, 'base64').toString();
BigQueryInsert(pubsubMessage, dataset, table);
};
async function BigQueryInsert(pubsubMessage, dataset, table) {
const rows = [{ field1: pubsubMessage }];
await bigqueryClient.dataset(dataset).table(table).insert(rows);
}
index.test.js:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
describe('66267929', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
const bigqueryClientStub = {
dataset: sinon.stub().returnsThis(),
table: sinon.stub().returnsThis(),
insert: sinon.stub().resolves(),
};
const googleCloundBigqueryStub = {
BigQuery: sinon.stub().returns(bigqueryClientStub),
};
const { sendtobigquery } = proxyquire('./', {
'#google-cloud/bigquery': googleCloundBigqueryStub,
});
const data = Buffer.from('teresa teng').toString('base64');
sendtobigquery({ data });
await flushPromises();
sinon.assert.calledOnce(googleCloundBigqueryStub.BigQuery);
sinon.assert.calledWithExactly(bigqueryClientStub.dataset, 'dataset_name');
sinon.assert.calledWithExactly(bigqueryClientStub.table, 'table_name');
sinon.assert.calledWithExactly(bigqueryClientStub.insert, [{ field1: 'teresa teng' }]);
});
});
unit test result:
66267929
✓ should pass (343ms)
1 passing (346ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
I'm trying to mock the crypto module of nodejs with the jestjs framework.
Here is the piece of code that I want to mock:
app.js:
const hash = crypto
.createHmac('sha256', API_TOKEN)
.update(JSON.stringify(body))
.digest('hex');
if(hash === signature) {
//verified successfully. Implement next logic
}
Here, I want the .digest function to give the value contained in the signature variable.
Following is the mock code:
jest.mock('crypto', () => {
return {
createHmac: jest.fn(() => ({
update: jest.fn(),
digest: jest.fn(() => '123') //here '123' is placeholder for 'signature value
}))
};
});
However, when running the test, the main files throws an error like this:
TypeError: Cannot read property 'update' of undefined
What am I missing here for jest mock?
You should use mockFn.mockReturnThis() to obtain method chain call.
E.g.
app.js:
const crypto = require('crypto');
function main() {
const API_TOKEN = 'API_TOKEN';
const signature = '123';
const body = {};
const hash = crypto
.createHmac('sha256', API_TOKEN)
.update(JSON.stringify(body))
.digest('hex');
if (hash === signature) {
console.log('verified successfully. Implement next logic');
}
}
module.exports = main;
app.spec.js:
const main = require('./app');
const crypto = require('crypto');
jest.mock('crypto', () => {
return {
createHmac: jest.fn().mockReturnThis(),
update: jest.fn().mockReturnThis(),
digest: jest.fn(() => '123'),
};
});
describe('64386858', () => {
it('should pass', () => {
const logSpy = jest.spyOn(console, 'log');
main();
expect(crypto.createHmac).toBeCalledWith('sha256', 'API_TOKEN');
expect(crypto.update).toBeCalledWith(JSON.stringify({}));
expect(crypto.digest).toBeCalledWith('hex');
expect(logSpy).toBeCalledWith('verified successfully. Implement next logic');
logSpy.mockRestore();
});
});
unit test result:
PASS src/stackoverflow/64386858/app.spec.js
64386858
✓ should pass (17ms)
console.log node_modules/jest-environment-jsdom/node_modules/jest-mock/build/index.js:866
verified successfully. Implement next logic
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 50 | 100 | 100 | |
app.js | 100 | 50 | 100 | 100 | 13 |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 5.087s, estimated 11s
I'm writing a Node application in TypeScript which is querying a Postgres database. In the unit tests I'm unable to mock the query-function.
My unit code looks like this:
class MyClass {
/* more stuff */
async validate(event: any): Promise<boolean> {
const client = new Client();
await client.connect();
const result = await client.query(`SELECT * FROM "user" WHERE "user_id" = '${user_id}'`);
console.log(
result
);
/* more logic */
}
}
My unit test looks like this:
describe("...", () => {
sinon.stub(pg.Client.prototype, "connect").resolves();
let stub = sinon.stub(pg.Client.prototype, "query").withArgs(
`SELECT * FROM "user" WHERE "user_id" = '1234'`,
sinon.match.any,
sinon.match.any
).resolves("asdf");
it("...", () => {
await myClassInstance.validate(event);
});
});
Not talking about the test expectations for now, what I would expect is "asdf" printed on the console. But I don't!
I've tried removing the .withArgs(...) completely and then I can see "asdf". (But obviously it doesn't help that much ...)
What I've also tried is printing stub.lastCall and I see
args: [ `SELECT * FROM "user" WHERE "user_id" = 'user_with_verified_subscription'` ]
somewhere then ...
Removing the two sinon.match.any matchers don't help either because then the TS compiler is complaining that Client.query takes 3 arguments ...
Any help is highly appreciated!
Best regards,
Max
Here is the unit test solution using proxyquire and sinon:
index.ts:
import { Client } from 'pg';
class MyClass {
async validate(event: any) {
const user_id = 1;
const client = new Client();
await client.connect();
const result = await client.query(`SELECT * FROM "user" WHERE "user_id" = '${user_id}'`);
console.log(result);
}
}
export default MyClass;
index.test.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
describe('60172091', () => {
it('should valdiate', async () => {
const pgClientStub = {
connect: sinon.stub().returnsThis(),
query: sinon
.stub()
.withArgs(`SELECT * FROM "user" WHERE "user_id" = '1'`)
.resolves('asdf'),
};
const pgStub = sinon.stub().callsFake(() => pgClientStub);
const MyClass = proxyquire('./index', {
pg: { Client: pgStub },
}).default;
const logSpy = sinon.spy(console, 'log');
const instance = new MyClass();
await instance.validate();
sinon.assert.calledOnce(pgStub);
sinon.assert.calledOnce(pgClientStub.connect);
sinon.assert.calledWithExactly(pgClientStub.query, `SELECT * FROM "user" WHERE "user_id" = '1'`);
sinon.assert.calledWithExactly(logSpy, 'asdf');
});
});
Unit test results with 100% coverage:
60172091
asdf
✓ should valdiate (2212ms)
1 passing (2s)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.ts | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
What would be the proper way to unit test this getall function using mocha/chai? I'm having a hard time understanding what to expect with Promise.all.
const Promise = require('bluebird');
const someApiService = require('./someapiservice');
const _ = require('underscore');
function getall(arr) {
let promises = _.map(arr, function(item) {
return someApiService(item.id);
});
return Promise.all(promises);
}
We should stub standalone function someApiService use link seams. This is the CommonJS version, so we will be using proxyquire to construct our seams. Additionally, I use sinon.js to create the stub for it.
E.g.
getall.js:
const Promise = require('bluebird');
const someApiService = require('./someapiservice');
const _ = require('underscore');
function getall(arr) {
let promises = _.map(arr, function (item) {
return someApiService(item.id);
});
return Promise.all(promises);
}
module.exports = getall;
someapiservice.js:
module.exports = function someApiService(id) {
return Promise.resolve('real data');
};
getall.test.js:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const { expect } = require('chai');
describe('45337461', () => {
it('should pass', async () => {
const someapiserviceStub = sinon.stub().callsFake((id) => {
return Promise.resolve(`fake data with id: ${id}`);
});
const getall = proxyquire('./getall', {
'./someapiservice': someapiserviceStub,
});
const actual = await getall([{ id: 1 }, { id: 2 }, { id: 3 }]);
expect(actual).to.deep.equal(['fake data with id: 1', 'fake data with id: 2', 'fake data with id: 3']);
sinon.assert.calledThrice(someapiserviceStub);
});
});
unit test result:
45337461
✓ should pass (2745ms)
1 passing (3s)
-------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------------|---------|----------|---------|---------|-------------------
All files | 88.89 | 100 | 66.67 | 88.89 |
getall.js | 100 | 100 | 100 | 100 |
someapiservice.js | 50 | 100 | 0 | 50 | 2
-------------------|---------|----------|---------|---------|-------------------