Sinon Spy and Stub function through actual http request - javascript

I have a function which is called from an API. I can stub it using sinon if I directly call it in my test for example foo.bar(); But if I call it through a test that makes an http request to that function it doesn't get stubbed. Any ideas?

You need to be able to start your application from the tests, and you should structure you application in a way that you can inject the dependencies that you want to control.
In the code below I have tried showing how you can use link seams (using proxyquire) to control the dependencies, but you could also use direct dependency injection to your app (say, pass it in to the start() method) as an alternative.
The code below is instructive, not necessarily working, so it skips setting up http listeners properly, etc. That is left as an exercise for the reader ;)
app.js
const myModule = require('./my-module');
if (require.main === module) {
/// starting from the cli
start({});
}
module.exports = {
start(){
app.get(`/`, (req, res) => {
myModule.foo(req.data).then(res.send).catch(res.send);
});
});
stop(){
app.stop(); // somehow kill the http listener and shutdown
}
}
test.js
const stub = sinon.stub();
const myApp = proxyquire('../app.js',{'./my-module': {foo: stub } });
before(()=> {
myApp.start();
});
it('should call a sinon stub', (done) => {
request('localhost:3000/')
.then( result = > expect(stub.called).to.equal(true) )
.then(done, done);
});

Related

How to start an express server using testEnvironment as NodeEnvironment?

After looking into the questions:
Async setup of environment with Jest
window/document not defined in import
Configure Jest global tests setup with .ts file (TypeScript)
About app.listen() callback
How to write a Jest configuration file
NodeJS: How to get the server's port?
https://alligator.io/nodejs/serving-static-files-in-express/
Promisify server.listen
Unexpected token import in custom node environment
How to access class properties of Jest Test Environment inside child test?
Cannot create custom TestEnvironment in Jest
globalSetup is executed in different context than tests
global beforeAll
How to test url change with Jest
Specify window.location for each test file for Jest
window.location.href can't be changed in tests
global beforeAll
How do I test a single file using Jest?
https://basarat.gitbook.io/typescript/intro-1/jest
I was able to do this:
package.json
{
"name": "my-project",
"jest": {
"testEnvironment": "./testEnvironment.js",
}
}
testEnvironment.js
const express = require('express');
// const NodeEnvironment = require('jest-environment-node'); // for server node apps
const NodeEnvironment = require('jest-environment-jsdom'); // for browser js apps
class ExpressEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context);
}
async setup() {
await super.setup();
const app = express();
this.global.server = app.listen(0, "127.0.0.1", () => {
console.log(`Running express server on '${JSON.stringify(server.address())}'...`);
how to make setup() wait until app.listen callback is finished,
i.e., the server has properly started.
});
app.use(express.static('../testfiles'));
}
async teardown() {
this.global.server.close();
await super.teardown();
}
runScript(script) {
return super.runScript(script);
}
}
module.exports = ExpressEnvironment;
How to make setup() wait until app.listen() callback is finished, i.e., the server has properly started?
Before, when I was using beforeAll(), my code was working fine because I could use the done() async callback passed by beforeAll():
const express = require('express');
const app = express();
var server;
beforeAll(async (done) => {
server = app.listen(0, "127.0.0.1", () => {
console.log(`Running express server on '${JSON.stringify(server.address())}'...`);
done();
});
app.use(express.static('../testfiles'));
});
afterAll(() => {
server.close();
});
How would be the equivalent to the beforeAll done() callback on the NodeEnvironment setup() function?
You can do this by awaiting the listen even, wrapping it in a promise, and calling the promise resolve as the callback to the server listen
const app = express();
let server;
await new Promise(resolve => server = app.listen(0, "127.0.0.1", resolve));
this.global.server = server;
You could also put a custom callback that will just call the promise resolver as the third argument to the app.listen() and it should run that code then call resolve if you need some sort of diagnostics.
Extending Robert Mennell answer:
You could also put a custom callback that will just call the promise resolver as the third argument to the app.listen() and it should run that code then call resolve if you need some sort of diagnostics.
let server;
const app = express();
await new Promise(function(resolve) {
server = app.listen(0, "127.0.0.1", function() {
console.log(`Running express server on '${JSON.stringify(server.address())}'...`);
resolve();
});
});
this.global.server = server;
Then, you can access the this.global.server in your tests files to get the server port/address: Is it possible to create an Express.js server in my Jest test suite?

How do I change service requires to mock requires when running unit tests in NodeJS

Consider I have the following code in my server file:
// Internal modules
const authenticationService = require('../services/authenticationService');
// Setup
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
// Init routing
require('./controllers/authenticationController')(app, authenticationService);
app.listen(8081, (err) => {
if (err) throw new Error('Could not start the server');
});
Now this is fine and will work. But what if I run unit tests and want to replace authenticationService with a mocked version? I don't want to hit my real database when executing tests against my API. Am I structuring this wrong or how should I approach this? I know I can use different mocking modules to fake the authenticationService away but honestly I don't enjoy using sinon etc too much. I'd rather write my own mock services this time. Any ideas / help?
Sinon is a good way to build an mock/stub Object.
But it need you to inject the mock/stub object into your servrice code and the premise is that the code is consistent with the dependency inversion principle
So I think the rewire module could help you. By using rewire you can make overriding dependencies easy without modifying existing code.
What you need is dependency injection. Instead of directly doing
require('../services/authenticationService');
in your module, your module should accept authenticationService as a dependency. So if you encapsulate your code into a module, say SomeModule.js:
module.exports = function SomeModule(authenticationService, app, otherDependencies /* such as bodyParser etc. as needed. */) {
this.start = function(port) {
// your code goes here
// more code
app.listen(8081, (err) => {
if (err) throw new Error('Could not start the server');
});
}
}
then in your production code you can call it like this:
var myModule = new SomeModule(require('../services/authenticationService'), app /* and all other dependencies required by SomeModule.*/);
module.start(8081);
and in your test code you can call it like this:
var myModule = new SomeModule(require('../mocks/mockAuthenticationService'), app /* and all other dependencies required by SomeModule.*/);
module.start(8081);

'window is not defined' when testing Paho MQTT Client with mocha and typescript

I googled for days but can't find anything about how to test the Paho MQTT Client. I tried it in a sort of naive way, like that:
import { suite, test, slow, timeout, skip, only } from 'mocha-typescript';
import chai = require('chai');
var jsdom = require('jsdom');
var Paho: any;
const expect: any = chai.expect;
const host: string = '127.0.0.1';
const port: number = 1384;
const clientId1: string = 'testid1';
const clientId2: string = 'testid2';
let client1;
let client2;
describe('test', function () {
it('should', function (done) {
// emulate browser window, which is required by Paho
jsdom.env("<html><body></body></html>", [],
function (err: any, window: any) {
// when window is ready, require Paho and
// initialize with built window
Paho = require('ng2-mqtt/mqttws31').jsdom(window);
// This does not work -> exception in mqttws31.js: window is not defined
client1 = new Paho.MQTT.Client(host, port, clientId1);
client1.connect({ onSuccess: () => { expect(true).to.be.true; done(); }, onFailure: () => { expect(false).to.be.true; } })
done();
});
});
});
However the Paho = require(...)-Part inside the callback function of jsdom.env(...) throws the exception in mqttws31.js: "window is not defined". Has anyone an idea how to solve that, in order to get the Paho Client running in a non-browser environment?
Thanks in advance!
https://www.npmjs.com/package/mochify You could be using similiar NPM module like this. Like you might expect Node.js environment dosen't have browser globals itself so use some library which can integrate these globals to your testing environment.
I am not very familiar with mocha but here's one more library I have played around in Karma as reference https://www.npmjs.com/package/karma-browserify
Or simply using some external service like BrowserStack https://www.npmjs.com/package/browserstack

Chai assertions not executed inside JSDOM Env block

I've just started writing my front-end unit tests with mocha, chai and jsdom. When I try to follow the tutorials based on those - I'm getting passing tests even when I set them to fail. Everything else is setup and works as expected jQuery, setup.js, window etc. The only issue is that my Assertions are not getting executed at all inside the jsdom env block.
This is my test:
var chai = require('chai'),
expect = chai.expect,
jsdom = require('jsdom'),
fs = require('fs'),
jquery = fs.readFileSync('./js/vendor/jquery-3.0.0.min.js', 'utf-8');
describe('UI DOM tests', function () {
it('should fail', function () {
// simple html
var htmlFragment = fs.readFileSync('./test/runner.html');
jsdom.env({
html: htmlFragment,
src: [jquery, '../node_modules/chai/chai.js'],
done: function (err, window) {
var $ = window.$;
expect(true).eql(false); // nothing happens
done();
}
});
expect(true).eql(false); // assert fails as expected
});
});
Any help is greatly appreciated.
it seems you're missing the done argument in your it:
it('should fail', function (done) {
otherwise mocha will think your it is synchronous and finish before your jsdom env is created.

Node.js Mocha Testing Restful API Endpoints and Code Coverage

I've been really enjoying Istanbul and experimenting with other Node.js coverage libraries as well, but I have an issue. Nearly all of my unit tests are HTTP calls to my API like so:
it('should update the customer', function (done) {
superagent.put('http://myapp:3000/api/customer')
.send(updatedData)
.end(function (res) {
var customer = res.body;
expect(res.statusCode).to.equal(200);
expect(customer.name).to.equal(updatedData.name);
done();
});
});
As opposed to actually requiring the customers.js file and calling updateCustomer directly. Testing the endpoint makes much more sense to me, as it not only tests updateCustomer, but also the routes, controllers, and everything else involved.
This works fine, but the problem is that I can't seem to see a way for any code coverage tool to recognize these tests. Is there any way for Istanbul or anything else to recognize these Mocha tests? If not, what is the convention? How do you test endpoints and still use code coverage tools?
The issue is that you're using superagent, whereas you should be using supertest to write unit tests. If you use supertest, istanbul will correctly track code coverage.
Some sample code to get you started:
'use strict';
var chai = require('chai').use(require('chai-as-promised'));
var expect = chai.expect;
var config = require('../../config/config');
var request = require('supertest');
var app = require('../../config/express')();
describe('Test API', function () {
describe('test()', function() {
it('should test', function(done) {
request(app)
.get('/test')
.query({test: 123})
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res){
expect(err).to.equal(null);
expect(res.body).to.equal('whatever');
done();
});
});
it('should return 400', function(done) {
request(app)
.get('/test/error')
.query({})
.expect('Content-Type', /json/)
.expect(400, done);
});
});
});

Categories