How to properly write unit tests for router modules in NodeJS - node.js

I'm new to nodeJS and testing and I'd like to know how I can test my application routes properly. I've read some articles that uses supertest and chai-http to call the POST method but I believe this way would be more of an integration testing instead of unit testing my app.
I've read about Sinon but I'm having a hard time applying it on my code like I don't know what to stub, how I can manipulate the data from the request body so I can cover different branches of my conditional statements. I'm monitoring my code coverage with nyc so I'm also aiming to increase my unit test coverage.
I would appreciate it a lot if someone can guide me on this. Thanks in advance!
server.js
const express = require('express');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.listen(8080, function () {
logger.info('App is now running on port 8080');
});
app.use('/', require('./routes/generateURL.js'));
module.exports = app;
generateURL.js
const express = require('express');
const router = express.Router();
router.post('/generate-url', (req, res) => {
let result = {
status: false
};
const acctId = req.body.accountId;
const baseURL = 'http://somecompany.com/billing/';
const dateToday = new Date();
try {
if (accountId) {
result.status = true;
result.bill = baseURL + acctId + '/' + dateToday;
} else {
throw 'Missing accountId';
}
} catch(err){
console.log(err);
}
return res.send(result);
});
module.exports = router;

Related

How do I grab json from an external api (serp api) from my backend and then make that same data available for my front end application?

Right now I have a front end react application using axios and and a backend server using node.js and express. I cannot for the life of me get my serp api data to post so that my front end can get it through axios and display the json data. I know how to get data to the front end but I am not a backend developer so this is proving to be incredibly difficult at the moment. I'm able to get the data from the the external api, I just don't know how to post it once I get it. Also I would not like to have all these request running on server.js so I created a controller but I think that is where it is messing up. Any help is appreciated
//pictures controller
const SerpApi = require('google-search-results-nodejs');
const {json} = require("express");
const search = new SerpApi.GoogleSearch("674d023b72e91fcdf3da14c730387dcbdb611f548e094bfeab2fff5bd86493fe");
const handlePictures = async (req, res) => {
const params = {
q: "Coffee",
location: "Austin, Texas, United States",
hl: "en",
gl: "us",
google_domain: "google.com"
};
const callback = function(data) {
console.log(data);
return res.send(data);
};
// Show result as JSON
search.json(params, callback);
//res.end();
}
// the above code works. how do i then post it to the server so that i can retrieve it to the backend?
module.exports = {handlePictures};
//server.js
const express = require('express');
const app = express();
const path = require('path');
const cors = require('cors');
const corsOptions = require('./config/corsOptions');
const { logger } = require('./middleware/logEvents');
const errorHandler = require('./middleware/errorHandler');
const cookieParser = require('cookie-parser');
const credentials = require('./middleware/credentials');
const PORT = process.env.PORT || 3500;
// custom middleware logger
app.use(logger);
// Handle options credentials check - before CORS!
// and fetch cookies credentials requirement
app.use(credentials);
// Cross Origin Resource Sharing
app.use(cors(corsOptions));
// built-in middleware to handle urlencoded form data
app.use(express.urlencoded({ extended: false }));
// built-in middleware for json
app.use(express.json());
//middleware for cookies
app.use(cookieParser());
//serve static files
app.use('/', express.static(path.join(__dirname, '/public')));
// routes
app.use('/', require('./routes/root'));
app.use('/pictures', require('./routes/api/pictures'));
app.all('*', (req, res) => {
res.status(404);
if (req.accepts('html')) {
res.sendFile(path.join(__dirname, 'views', '404.html'));
} else if (req.accepts('json')) {
res.json({ "error": "404 Not Found" });
} else {
res.type('txt').send("404 Not Found");
}
});
app.use(errorHandler);
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
//api/pictures.js
const picturesController= require('../../controllers/picturesController');
const express = require('express')
const router = express.Router();
// for POST request use app.post
router.route('/')
.post( async (req, res) => {
// use the controller to request external API
const response = await picturesController.handlePictures()
// send the response back to client
res.json(response)
})
module.exports = router;
You just need to return the result from SerpApi in your handlePictures function. To do this make a new Promise and when search.json runs callback do what you need with the results and pass it in resolve.
Your picturesController.js with an example of returning all results.
//pictures controller
const SerpApi = require("google-search-results-nodejs");
const { json } = require("express");
const search = new SerpApi.GoogleSearch(process.env.API_KEY); //your API key from serpapi.com
const handlePictures = async (req, res) => {
return new Promise((resolve) => {
const params = {
q: "Coffee",
location: "Austin, Texas, United States",
hl: "en",
gl: "us",
google_domain: "google.com",
};
const callback = function(data) {
resolve(data);
};
search.json(params, callback);
});
};
module.exports = { handlePictures };
Output:
And I advise you to change your API key to SerpApi to prevent it from being used by outsiders.
Since I don't have the full context of your App I can just assume the context. But given the fact that you already have wrapped the logic of calling the external API into a dedicated controller you can use it in the following way in an express app (used the hello world example from express):
// import your controller here
const express = require('express')
const app = express()
const port = 3000
// for POST request use app.post
app.get('/', async (req, res) => {
// use the controller to request external API
const response = await yourController.method()
// send the response back to client
res.json(response)
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Here's an example how to execute the http request from the frontend:
const response = await fetch('http://localhost:3000') // result from res.json(response)

Server initiated server-client websocket messages in nodejs?

I have a node js app that receives data from a sensor device via POST requests. It also serves frontend to monitor that data. I want it to send data updates via websocket to all connected clients
Here's what I came up with:
const express = require('express');
const app = express();
const expressWs = require('express-ws')(app);
const ws = expressWs.getWss('/ws');
function sendAll(data) {
ws.clients.forEach(function each(client) {
client.send(JSON.stringify(data));
});
}
setInterval(async () => {
try {
let message = { message: 'dataUpdated', data: { foo: 'bar } };
sendAll(message);
} catch (err) {
console.error(err);
}
}, 1000 * 5);
app.use(express.static('./static/'));
// tell the app to parse HTTP body messages
app.use(bodyParser.json());
// routes
const apiRoutes = require('./api/routes/api-routes');
app.use('/api', apiRoutes);
module.exports = app;
I can use the sendAll function to broadcast data. But I'd like to be able to also use this function inside apiRoutes where I process the incoming requests from the sensor. How can I pass it there, or maybe get access to the expressWs instance and create same function in the included api-routes file?
The solution turned out to be quite simple =)
const express = require('express');
const app = express();
const expressWs = require('express-ws')(app);
const ws = expressWs.getWss('/ws');
export function wsBroadcast(data) {
ws.clients.forEach(function each(client) {
client.send(JSON.stringify(data));
});
}
app.use(express.static('./static/'));
// tell the app to parse HTTP body messages
app.use(bodyParser.json());
// routes
const apiRoutes = require('./api/routes/api-routes');
app.use('/api', apiRoutes);
module.exports = app;
And then just import it in the file that needs it to broadcast messages:
import express from 'express';
import { wsBroadcast } from '../server';
const router = express.Router();
router.post('/data', (req, res) => {
wsBroadcast({ message: 'gotData', data: req.body });
...

Why is express.urlencoded not a function while testing with Jest?

I am testing my app.js using Jest and it says express.urlencoded is not a function.
Im using express 4.16.4 and while I am writing test for app.js that looks somewhat like this
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(require('./routes'));
module.exports = app;
and my test case is:
jest.mock('express');
it('has a parser', () => {
const app = require('../src/app');
const express = require('express');
expect(app.use).toHaveBeenCalledWith(express.urlencoded());
});
This gives me an error saying: express.urlencoded is not a function.
I expect this test to pass because the app works perfectly fine but only inside the test it says that express.urlencoded is not a function.
You have to manually provide factory and mock module methods there, which you want to test. This would look something like this:
const urlencodedMock = jest.fn();
jest.mock('express', () => {
urlencoded: urlencodedMock,
});
it('has a parser', () => {
const app = require('../src/app');
const express = require('express');
expect(app.use).toHaveBeenCalledWith(urlencodedMock);
});
EDIT:
I am not familiar with express, but you can try something like example below. jest.mock() will mock express in a way that it passes express() and then inside the test when that part is passed, it will mock it again, to provide json() and urlencoded() functions to express.
jest.mock('express', () => {
return {
use: () => {
return;
}
}
});
it('has a parser', () => {
const jsonMock = jest.fn();
const urlencodedMock = jest.fn();
const app = require('../src/app');
jest.doMock('express', () => {
json: jsonMock,
urlencoded: urlencodedMock,
});
expect(app.use).toHaveBeenCalledWith(urlencodedMock);
});

NodeJS unable to get

new to NodeJS and I am trying to get a basic endpoint going. I actually have three different controllers, but one of them will not work. Here is my app.js
var express = require('express');
var app = express();
var db = require('./db');
var config = require('./config/config'); // get config file
global.__root = __dirname + '/';
var ApiController = require(__root + 'auth/ApiController');
app.use('/api/auth', ApiController);
var UserController = require(__root + 'user/UserController');
app.use('/api/users', UserController);
var AuthController = require(__root + 'auth/AuthController');
app.use('/api/auth/users', AuthController);
module.exports = app;
The UserController and AuthController work great but the ApiController:
//this controller handles api token
var express = require('express');
var router = express.Router();
var bodyParser = require('body-parser');
router.use(bodyParser.urlencoded({ extended: false }));
router.use(bodyParser.json());
router.get('/apiToken') , function(req, res) {
console.log("received request at /apiToken");
res.status(200).send({ token: config.api.token });
};
module.exports = router;
When I try this in Postman you can see:
I know it has to be something really simple because the failing call is nearly identical to the working ones - but I just don't see it.
You have a coding mistake here with a closing paren in the wrong place on this line that happens to not make an interpreter error, but does not execute as intended:
router.get('/apiToken') , function(req, res) {
// here ^
So change this:
router.get('/apiToken') , function(req, res) {
console.log("received request at /apiToken");
res.status(200).send({ token: config.api.token });
};
to this:
router.get('/apiToken', function(req, res) {
console.log("received request at /apiToken");
res.send({ token: config.api.token });
});
FYI, there is no need to do res.status(200) as that is the default status already. You can just use res.send(...) and the status will be set to 200 automatically. You only need to use res.status(xxx) when you want the status to be something other than 200.
Also, running your code though something like jshint will often complain about these types of mistakes that (somewhat by accident) don't throw an interpreter error.

Unit Test Mongoose Promises

I have an express route in a Node application that uses Mongoose for querying a Mongo database. There is a promise used in the get request to find items and I am not sure how to test this promise.
I am not very familiar with unit testing node applications.
Here is the code for the express route, this works but I am not sure how to test it:
var express = require('express');
var router = express.Router();
var promise = require('bluebird');
// bluebird.promisifyAll is used on Mongoose in app.js
var ItemModel = require('./itemModel');
router.get('/', function(req, res, next) {
ItemModel.findAsync()
.then(function(items) {
res.status(200).send(items);
})
.catch(function(err) {
next(err);
});
});
module.exports = router;
Here is what I have stubbed out in the test so far. I am not sure how to test the promise in the routes.get method :
describe('ItemRoute', function() {
var express = require('express');
var bodyParser = require('bodyParser');
var supertest = require('supertest');
var sinon = require('sinon');
var expect = require('chai').expect;
var assert = require('chai').assert;
var ItemModel = require('./ItemModel');
var ItemRoute = require('ItemRoute');
var uri = '/items/';
var agent;
beforeEach(function(done) {
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(uri, releaseNotesRoute);
agent = supertest.agent(app);
done();
});
describe('GET', function() {
it('returns an empty array when no items are stored in Mongo', function() {
// Not sure how to test the route here with a get that
// uses a promise, ItemModel.findAsync().then
});
});
});
to able to use promises in test, you should have to install sinon-as-promises module from npm. Then you can mock ItemModel like this:
var itemModelMock = sinon.mock(ItemModel);
itemModelMock.expects('findAsync').resolves([]);

Resources