I have an Express app that uses node-slack-sdk to make posts to Slack when certain endpoints are hit. I am trying to write integration tests for a route that, among many other things, calls a method from that library.
I would like to prevent all default behavior of certain methods from the Slack library, and simply assert that the methods were called with certain arguments.
I have attempted to simplify the problem. How can I stub a method (which is actually nested within chat) of an instance of an WebClient, prevent the original functionality, and make assertions about what arguments it was called with?
I've tried a lot of things that haven't worked, so I'm editing this and providing a vastly simplified set-up here:
index.html:
const express = require('express');
const {WebClient} = require('#slack/client');
const app = express();
const web = new WebClient('token');
app.post('/', (req, res) => {
web.chat.postMessage({
text: 'Hello world!',
token: '123'
})
.then(() => {
res.json({});
})
.catch(err => {
res.sendStatus(500);
});
});
module.exports = app;
index.test.html
'use strict';
const app = require('../index');
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const {WebClient} = require('#slack/client');
describe('POST /', function() {
before(function() {
// replace WebClient with a simplified implementation, or replace the whole module.
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
// assert that web.chat.postMessage was called with {message: 'Hello world!'}, etc
});
});
});
There are a few things that make this difficult and unlike other examples. One, we don't have access to the web instance in the tests, so we can't stub the methods directly. Two, the method is buried within the chat property, web.chat.postMessage, which is also unlike other examples I've seen in sinon, proxyquire, etc documentation.
The design of your example is not very testable which is why you're having these issues. In order to make it more testable and cohesive, it's better to pass in your WebClient object and other dependencies, rather than create them in your route.
const express = require('express');
const {WebClient} = require('#slack/client');
const app = express();//you should be passing this in as well. But for the sake of this example i'll leave it
module.exports = function(webClient) {
app.post('/', (req, res) => {
web.chat.postMessage({
text: 'Hello world!',
token: '123'
})
.then(() => {
res.json({});
})
.catch(err => {
res.sendStatus(500);
});
})
return app;
};
In order to implement this, build your objects/routes at a higher module. (You might have to edit what express generated for you. I'm not sure, personally I work with a heavily refactored version of express to fit my needs.) By passing in your WebClient you can now create a stub for your test.
'use strict';
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const {WebClient} = require('#slack/client');
const web = new WebClient('token');
let app = require('../index')(web);
describe('POST /', function() {
it('should call chat.update with specific arguments', function() {
const spy = sinon.spy();
sinon.stub(web.chat, 'postMessage').callsFake(spy);
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
assert(spy.calledWith({message: 'Hello world!'}));
});
});
});
This is known as Dependency Injection. Instead of having your index module build it's dependency, WebClient, your higher modules will pass in the dependency in order for the them to control the state of it's lower modules. Your higher module, your test, now has the control it needs to create a stub for the lower module, index.
The code above was just quick work. I haven't tested to see if it works, but it should answer your question.
So #Plee, has some good points in term of structuring. But my answer is more about the issue at hand, how to make the test work and things you need to understand. For getting better at writing unit tests you should use other good resources like books and articles, I assume there would be plenty of great resources online for the same
The first thing you do wrong in your tests is the first line itself
const app = require('../index');
Doing this, you load the index file which then executes the below code
const {WebClient} = require('#slack/client');
const app = express();
const web = new WebClient('token');
So now the module has loaded the original #slack/client and created an object which is not accessible outside the module. So we have lost our chance of customizing/spying/stubbing the module.
So the first thumb rule
Never load such modules globally in the test. Or otherwise never load them before stubbing
So next we want is that in our test, we should load the origin client library which we want to stub
'use strict';
const {WebClient} = require('#slack/client');
const sinon = require('sinon');
Now since we have no way of getting the created object in index.js, we need to capture the object when it gets created. This can be done like below
var current_client = null;
class MyWebClient extends WebClient {
constructor(token, options) {
super(token, options);
current_client = this;
}
}
require('#slack/client').WebClient = MyWebClient;
So now what we do is that original WebClient is replaced by our MyWebClient and when anyone creates an object of the same, we just capture that in current_client. This assumes that only one object will be created from the modules we load.
Next is to update our before method to stub the web.chat.postMessage method. So we update our before method like below
before(function() {
current_client = null;
app = require('../index');
var stub = sinon.stub();
stub.resolves({});
current_client.chat.postMessage = stub;
});
And now comes the testing function, which we update like below
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
expect(current_client.chat.postMessage
.getCall(0).args[0]).to.deep.equal({
text: 'Hello world!',
token: '123'
});
});
});
and the results are positive
Below is the complete index.test.js I used, your index.js was unchanged
'use strict';
const {WebClient} = require('#slack/client');
const sinon = require('sinon');
var current_client = null;
class MyWebClient extends WebClient {
constructor(token, options) {
super(token, options);
current_client = this;
}
}
require('#slack/client').WebClient = MyWebClient;
const chai = require('chai');
const chaiHttp = require('chai-http');
const expect = chai.expect;
chai.use(chaiHttp);
let app = null;
describe('POST /', function() {
before(function() {
current_client = null;
app = require('../index');
var stub = sinon.stub();
stub.resolves({});
current_client.chat.postMessage = stub;
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
expect(current_client.chat.postMessage
.getCall(0).args[0]).to.deep.equal({
text: 'Hello world!',
token: '123'
});
});
});
});
Based on the other comments, it seems like you are in a codebase where making a drastic refactor would be difficult. So here is how I would test without making any changes to your index.js.
I'm using the rewire library here to get and stub out the web variable from the index file.
'use strict';
const rewire = require('rewire');
const app = rewire('../index');
const chai = require('chai');
const chaiHttp = require('chai-http');
const sinon = require('sinon');
const expect = chai.expect;
chai.use(chaiHttp);
const web = app.__get__('web');
describe('POST /', function() {
beforeEach(function() {
this.sandbox = sinon.sandbox.create();
this.sandbox.stub(web.chat);
});
afterEach(function() {
this.sandbox.restore();
});
it('should call chat.update with specific arguments', function() {
return chai.request(app).post('/').send({})
.then(function(res) {
expect(res).to.have.status(200);
const called = web.chat.postMessage.calledWith({message: 'Hello world!'});
expect(called).to.be.true;
});
});
});
Related
For example, if I have main.js calling a defined in src/lib/a.js, and function a calls node-uuid.v1, how can I stub node-uuid.v1 when testing main.js?
main.js
const a = require("./src/lib/a").a
const main = () => {
return a()
}
module.exports = main
src/lib/a.js
const generateUUID = require("node-uuid").v1
const a = () => {
let temp = generateUUID()
return temp
}
module.exports = {
a
}
tests/main-test.js
const assert = require("assert")
const main = require("../main")
const sinon = require("sinon")
const uuid = require("node-uuid")
describe('main', () => {
it('should return a newly generated uuid', () => {
sinon.stub(uuid, "v1").returns("121321")
assert.equal(main(), "121321")
})
})
The sinon.stub(...) statement doesn't stub uuid.v1 for src/lib/a.js as the above test fails.
Is there a way to globally a library function so that it does the specified behavior whenever it gets called?
You should configure the stub before importing the main module. In this way the module will call the stub instead of the original function.
const assert = require("assert")
const sinon = require("sinon")
const uuid = require("node-uuid")
describe('main', () => {
it('should return a newly generated uuid', () => {
sinon.stub(uuid, "v1").returns("121321")
const main = require("../main")
assert.equal(main(), "121321")
})
})
Bear in mind that node-uuid is deprecated as you can see by this warning
[Deprecation warning: The use of require('uuid') is deprecated and
will not be supported after version 3.x of this module. Instead, use
require('uuid/[v1|v3|v4|v5]') as shown in the examples below.]
About how to stub that for testing would be a bit more harder than before as actually there is no an easy way to mock a standalone function using sinon
Creating a custom module
//custom uuid
module.exports.v1 = require('uuid/v1');
Requiring uuid from the custom module in your project
const uuid = require('<path_to_custom_module>');
Sinon.stub(uuid, 'v1').returns('12345');
I'm playing around with a side practice software, and I'm trying to learn how to use Jest for testing. But I get an error when it should clean up the testing suite, I suspect because of async code.
When I run the Jest CLI with --runInBand, it works great, but I want to understand and fix so it will work without the flag.
\
Example of one of the test files
const {Genre} = require('../../../models/genre');
const {mongoose} = require('../../../app');
const request = require('supertest');
let server;
describe('testing GET for genres', function() {
beforeEach( function() {
server = require('../../../app').server;
});
afterEach(async function() {
await Genre.deleteMany({});
await mongoose.connection.close();
server.close();
});
it('should create a genre',async function() {
let genre = {
name: "abcde"
};
const result = await request(server).post('/api/genres').send(genre);
return expect(result).toBeDefined();
}) ;
});
Example of the other one:
const {User} = require('../../../models/user');
const request = require('supertest');
const {mongoose} = require('../../../app');
let server;
describe('testing POST for users', function() {
beforeEach(function() {
server = require('../../../app').server;
});
afterEach(async function() {
await User.deleteMany({});
await mongoose.connection.close();
server.close();
});
it('should create a user',async function() {
let user = {
email: "testing123#gmail.com",
password: "Yoyoyoy"
};
const result = await request(server).post('/api/users').send(user);
expect(result.status).toBe(200);
expect(result.body).toHaveProperty("email", "testing123#gmail.com");
expect(result.body).toHaveProperty("isAdmin", false);
}) ;
});
And when I run npm test, On of the 2 tests fail, and I get the error that the server is already running and also the error that I tried to do stuff/log after jest was shut down. Cleary this is a sync issue, can anyone help me to understand why, and how to control this?
In a previous project I mocked the mysql library with Sinon. I did this like so:
X.js:
const con = mysql.createPool(config.mysql);
...
Some other place in the project:
const rows = await con.query(query, inserts);
...
X.test.js:
const sinon = require('sinon');
const mockMysql = sinon.mock(require('mysql'));
...
mockMysql.expects('createPool').returns({
query: () => {
// Handles the query...
},
...
It worked perfectly.
In another project I am trying to mock pg, again with Sinon.
pool.js:
const { Pool } = require('pg');
const config = require('#blabla/config');
const pool = new Pool(config.get('database'));
module.exports = pool;
Some other place in the project:
const con = await pool.connect();
const result = await con.query(...
Y.test.js:
???
I can't understand how to mock connect().query(). None of the following approaches work:
1:
const { Pool } = require('pg');
const config = require('#blabla/config');
const mockPool = sinon.mock(new Pool(config.get('database')));
...
mockPool.expects('connect').returns({
query: () => {
console.log('query here');
},
});
1 results in no error but the real db connection is used.
2:
const { Pool } = sinon.mock(require('pg'));
const config = require('#blabla/config');
const pool = new Pool(config.get('database'));
pool.expects('connect').returns({
query: () => {
console.log('query here');
},
});
2 => TypeError: Pool is not a constructor
3:
const { Pool } = sinon.mock(require('pg'));
const config = require('#blabla/config');
const pool = sinon.createStubInstance(Pool);
pool.connect.returns({
query: () => {
console.log('query here');
},
});
3 => TypeError: The constructor should be a function.
Can anybody point me in the right direction with how to mock my PostgreSQL connection?
Example: I have postgres.js like this.
const { Pool } = require('pg');
const handler = {
count: async (pgQuery) => {
try {
const pool = new Pool();
const res = await pool.query(pgQuery);
return { count: parseInt(res.rows[0].counter, 10) };
} catch (error) {
// Log/Throw error here.
}
return false;
}
}
module.exports = handler;
The spec test I created on postgres.spec.js is like this.
const { expect } = require('chai');
const sinon = require('sinon');
const pgPool = require('pg-pool');
const handler = require('postgres.js');
describe('Postgres', function () {
it('should have method count that bla bla', async function () {
// Create stub pgPool query.
const postgreeStubQuery = sinon.stub(pgPool.prototype, 'query');
postgreeStubQuery.onFirstCall().throws('XXX');
postgreeStubQuery.onSecondCall().resolves({
rows: [{ counter: 11 }],
});
// Catch case.
const catcher = await handler.count('SELECT COUNT()..');
expect(catcher).to.equal(false);
expect(postgreeStubQuery.calledOnce).to.equal(true);
// Correct case.
const correct = await handler.count('SELECT COUNT()..');
expect(correct).to.deep.equal({ count: 11 });
expect(postgreeStubQuery.calledTwice).to.equal(true);
// Restore stub.
postgreeStubQuery.restore();
});
});
To stub pool.query(), you need to stub pg-pool prototype and method query.
Hope this helps.
Since you're needing to mock the returned results of a query, I think the easiest solution would be to abstract your database from the the code needing the query results. Example being, your query results are returning information about a person. Create a person.js module with specific methods for interacting with the database.
Your other code needing the person information from the database won't know or care what type of database you use or how you connect to it, all they care to know is what methods are exposed from person.js when they require it.
//person.js
const { Pool } = require('pg')
// do other database connection things here
const getPersonById = function (id) {
// use your query here and return the results
}
module.exports = { getPersonById }
Now in your tests, you mock the person module, not the pg module. Imagine if you had 20 some odd tests that all had the mock MySQL pool set up then you changed to pg, you'd have to change all of those, nightmare. But by abstracting your database connection type/setup, it makes testing much easier, because now you just need to stub/mock your person.js module.
const person = require('../person.js') //or whatever relative file path it's in
const sinon = require('sinon')
describe('person.js', function () {
it('is stubbed right now', function () {
const personStub = sinon.stub(person)
personStub.getPersonById.returns('yup')
expect(personStub.getPersonById()).to.eq('yup')
})
})
Below is a simpler approach that means the system-under-test doesn't need any special tricks.
It is comprised of two parts, though the first is "nice to have":
Use a DI framework to inject the pg.Pool. This is a better approach IMO anyway, and fits really well with testing.
In the beforeEach() of the tests, configure the DI framework to use a mock class with sinon.stub instances.
If you aren't using a DI framework, pass the mock as a Pool parameter... but DI is better ;)
The code below is TypeScript using tsyringe, but similar approaches will work fine with plain JavaScript etc.
Somewhere you'll have code that uses pg.Pool. A contrived example:
import { Pool } from 'pg'
...
function getPets(pool: Pool): Promise<Pet[]> {
return pool.connect()
.then(db => db.query(SQL_HERE)
.then(result => {
db.release()
return result.rows // or result.rows.map(something) etc
})
.catch(error => {
db.release()
throw error
})
)
}
That works, and it's fine if you want to pass the Pool instance in. I'd prefer not to, so I use tsyringe like this:
import { container } from 'tsyringe'
...
function getPets(): Promise<Pet[]> {
return container.resolve<Pool>().connect()
.then(...)
}
Exactly the same outcome, but getPets() is cleaner to call - it can be a pain to lug around a Pool instance.
The main of the program would set up an instance in one of a few ways. Here's mine:
...
container.register(Pool, {
useFactory: instanceCachingFactory(() => {
return new Pool(/* any config here */)
})
})
The beauty of this comes out in tests.
The code above (the "system under test") needs a Pool instance, and that instance needs a connect() method that resolves to a class with query() and release() methods.
This is what I used:
class MockPool {
client = {
query: sinon.stub(),
release: sinon.stub()
}
connect () {
return Promise.resolve(this.client)
}
}
Here's the setup of a test using MockPool:
describe('proof', () => {
let mockPool: MockPool
beforeEach(() => {
// Important! See:
// https://github.com/microsoft/tsyringe#clearing-instances
container.clearInstances()
mockPool = new MockPool()
container.registerInstance(Pool, mockPool as unknown as Pool)
})
})
The cast through unknown to Pool is needed because I'm not implementing the whole Pool API, just what I need.
Here's what a test looks like:
it('mocks postgres', async () => {
mockPool.client.query.resolves({
rows: [
{name: 'Woof', kind: 'Dog'},
{name: 'Meow', kind: 'Cat'}
]
})
const r = await getPets()
expect(r).to.deep.equal([
{name: 'Woof', kind: 'Dog'},
{name: 'Meow', kind: Cat'}
])
})
You can easily control what data the mock Postgres Pool returns, or throw errors, etc.
I'm trying to follow the PACT workshop example with some alternate data.
This may be more of a Javascript/Node question but I'm a but stumped, as a novice.
Given a consumer.spec.js file of:
const chai = require('chai');
const nock = require('nock');
const chaiAsPromised = require('chai-as-promised');
const expect = chai.expect;
const API_PORT = process.env.API_PORT || 9123;
chai.use(chaiAsPromised);
const API_HOST = `http://localhost:${API_PORT}`;
describe('Consumer', () => {
describe('when a call to the Provider is made', () => {
const clothingStatus = 'hello';
const {emailClothingOfferStatus} = require('../client');
it('can process the HTML payload from the provider', () => {
nock(API_HOST)
.get('/provider')
.query({validPermStatus:'hello'})
.reply(200, {
test:'NO',
validPermStatus: clothingStatus,
count: 1000,
});
const response = emailClothingOfferStatus(clothingStatus);
return expect(response.body.clothingStatus).to.eventually.equal('hello')
})
})
});
and a client .js file of:
const request = require('superagent');
const API_HOST = process.env.API_HOST || 'http://localhost';
const API_PORT = process.env.API_PORT || 9123;
const API_ENDPOINT = `${API_HOST}:${API_PORT}`;
// Fetch provider data
const emailClothingOfferStatus = emailPermChoice => {
let withEmailClothing = {};
const emailClothingGrantedRegex = 'hello';
if(emailPermChoice){
console.log(emailPermChoice);
withEmailClothing = {validPermStatus: emailPermChoice}
}
return request
.get(`${API_ENDPOINT}/provider`)
.query(withEmailClothing)
.then(
res => {
if (res.body.validPermStatus.match(emailClothingGrantedRegex)) {
return {
clothingStatus: (res.body.validPermStatus),
}
} else {
throw new Error('Could not verify email clothing offer status')
}
},
err => {
throw new Error(`Error from response: ${err.body}`)
}
)
};
module.exports = {
emailClothingOfferStatus,
};
and I have the following in my package.json scripts:
"test:consumer": "./node_modules/.bin/mocha --timeout 150000 pact/consumer/test/consumer.spec.js",
When I run npm run test:consumer, I get:
1) Consumer
when a call to the Provider is made
can process the HTML payload from the provider:
TypeError: Cannot read property 'clothingStatus' of undefined
at Context.it (pact/consumer/test/consumer.spec.js:29:35)
I'm sure it's something obvious but can anyone help?
Two things stand out to me as a problem:
The test above is a normal unit test designed to show how unit tests won't catch contract issues, and leads you into why Pact is useful (In case this wasn't clear). In short, it's not a Pact test at all - I can tell because it's using Nock, meaning the expected requests will never reach Pact. I can also tell because the Pact package doesn't appear to be imported. You want to model from this file https://github.com/DiUS/pact-workshop-js/blob/master/consumer/test/consumerPact.spec.js
The response value is a Promise, which means you can't do return expect(response.body.clothingStatus).to.eventually.equal('hello') because response is a promise, so body will be undefined and clothingStatus is not a property of that. The chai eventually API is useful for this sort of test, but as I understand, it has to work directly with a Promise - you could do expect(response).to... and then chai can go to work.
Your function emailClothingOfferStatus returns response.then() which is a promise and not an actual response.
Therefore response.body is undefined.
You should be able to test the result like this:
const response = emailClothingOfferStatus(clothingStatus);
response.then((res) => {
expect(res.body.clothingStatus).to.eventually.equal('hello')
})
I must have some fundamental problem understanding of how proxyquire works or doing something wrong.
For a proof of concept I have this original code connecting to neo4j graphnedb in node.js:
// I am lib/neo4j.js
var neo4j = require('neo4j-driver').v1;
var graphenedbURL = process.env.GRAPHENEDB_BOLT_URL;
var graphenedbUser = process.env.GRAPHENEDB_BOLT_USER;
var graphenedbPass = process.env.GRAPHENEDB_BOLT_PASSWORD;
var driver = neo4j.driver(graphenedbURL, neo4j.auth.basic(graphenedbUser, graphenedbPass));
Then I have this test:
// I am test/neo4j.test.js
'use strict';
const test = require('tap').test;
const proxy = require('proxyquire');
const sinon = require('sinon');
test('Testing connection to Neo4j', (assert) => {
const driverStub = sinon.stub();
const testedModule = proxy('../lib/neo4j', {
'neo4j': {
'driver': driverStub,
},
});
});
Test is run as npm tap test/*.test.js --conv
Because npm does not provide access to .env for heroku graphnedb the driver won't have any process.env connection variables which should be ok since my expectation is that proxyquire will replace the driver with above defined stub but that's not happening and the test fails on neo4j.driver missing graphnedebURL. What am I doing wrong please?
You need to proxyquire neo4j the same way you are requiring in the original file, including v1.
// I am test/neo4j.test.js
'use strict';
const test = require('tap').test;
const proxy = require('proxyquire');
const sinon = require('sinon');
test('Testing connection to Neo4j', (assert) => {
const driverStub = sinon.stub();
const testedModule = proxy('../lib/neo4j',
{
'neo4j-driver': {
'v1': {
driver: driverStub
},
},
});
});