How do I mock my config file for testing? - javascript

I have a Koa app I just started and I need to test something that grabs data from a config file.
I need to test with specific data, but I'm not sure how to modify what data the test receives from the config file.
Example:
app.js
var router = require('koa-router');
var config = require('./config.js');
var db = require('./db.js');
var auth = require('./auth');
var app = require('koa')();
router.get('/', function *() {
if(auth(this.req, config.credentials.secret)) { // Authenticates request based on a hash created using a shared secret
this.body = "Request has been authenticated";
}
});
app.use(router.routes());
app = module.exports = http.createServer(app.callback());
app.listen(3000);
appSpec.js
var request = require('supertest');
var app = require('../app.js');
describe('app', function() {
it('should authenticate all requests against config shared secret', function() {
var secret = 'some_secret';
var validHash = /* hash created from test secret and query */;
request(app)
.get('/')
.query({query: 'some_query'})
.query({hash: validHash})
.expect(403, done);
});
});
This spec will fail because the app will use the secret from the config file(empty string) instead of my test secret.

Alright, I played around with some different ways to handle this.
The best option I found, for my particular use case, was proxyquire. It's an npm package that lets you override dependencies in any file that you require in your test suites.
So if I am testing this module:
./controllers/myController.js
var config = require('../config.js');
module.exports = function() {
// Do some stuff
};
I would do something like this:
./test/controllers/myControllerSpec.js
var proxyquire = require('proxyquire');
var config = {
credentials: {
secret: 'my_secret'
}
// other fake config stuff
};
var myController = proxyquire('../../controllers/myController.js', {'../config.js', config});
describe('myController', function() {
// TESTS
});
and this instance of myController will use my test config.
This won't work for end to end testing, unless the only place you import your config is the main app file.

I use node-config for my config files and configuration loading based on machine or env variable.
You can specify your config in a variety of formats (.json, .js, yaml, etc.) Using the default settings, you need to create a config folder in your app root and a default.<format> with your default config.
To override that for testing you can create a test.<format> file in your config directory. When you set your NODE_ENV=test, then node-config will see load your default config file and then it will load your test config file and if there are any conflicts for the values, your test config file will override the values in your default file.
Here are the full docs for setting up Configuration Files in node-config
Below is an example using node-config with a .js config file format.
./config/default.js
module.exports = {
credentials: {
secret: ''
}
}
./config/test.js
module.exports = {
credentials: {
secret: 'abcdef123456'
}
}
app.js
var router = require('koa-router');
var config = require('config');
var db = require('./db.js');
var auth = require('./auth');
var app = require('koa')();
var credentials = config.get('credentials');
router.get('/', function *() {
if(auth(this.req, credentials.secret)) { // Authenticates request based on a hash created using a shared secret
this.body = "Request has been authenticated";
}
});
app.use(router.routes());
app = module.exports = http.createServer(app.callback());
app.listen(3000);

Jest has nice support for this case. In your test file, add
jest.mock('../config.js', () => ({
credentials: {
secret: 'my_secret'
}
// other fake config stuff }));

Related

Dynamic pathRewrite with createProxyMiddleware and create react app

I have the following in my Create React App as per https://create-react-app.dev/docs/proxying-api-requests-in-development/
src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};
This works fine and sends all requests to my nodejs app running on port 5000. However I wish to intercept the request somehow and rewrite the path into a url query string format.
I have json-server running on the nodejs server which needs the requests to be formatted differtently, using this type of format /api/cheeses?cheeseno=12
For example
'/api/cheese/12' => `/api/cheeses?cheeseno=12`
I have come across pathRewrite and router on this page https://www.npmjs.com/package/http-proxy-middleware but I have no idea how to map them over.
Later on as I get mor advanced, I will need to map nested path routes to url queries.
So
/location/{locationId}/aisle/{aisleid}/cheeses => /api/cheeses?locationId=12&aisleid=123`
Thanks
const { createProxyMiddleware } = require('http-proxy-middleware');
const rewriteFn = function (path, req) {
return path.replace('/api/foo', '/api/bar');
};
const options = {
target: 'http://localhost:3000',
pathRewrite: rewriteFn,
};
const apiProxy = createProxyMiddleware('/api', options);
rewriteFn is the key
https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/pathRewrite.md#custom-rewrite-function

How can I pass options into an imported module?

I have a utility module that creates an instance of a multer-gridfs storage engine for uploading files to my Mongo database. I use this module inside of any API route that requires the need to upload files.
I need to be able to update the metadata property value with a unique identifier. More than likely this will be the mongoose _id of the user uploading the file, but for now I am not concerned with that aspect of it. I really just want to know if I can change the metadata property dynamically.
Here is the storage engine gridFs_upload_engine.js:
const mongoose = require('mongoose');
const path = require('path');
const crypto = require('crypto');
const multer = require('multer');
const GridFsStorage = require('multer-gridfs-storage');
const Grid = require('gridfs-stream');
//Init Upload Engine
let gfs;
//Global instance of the DB connection
const database = mongoose.connection;
const mongoDb = process.env.MONGODB_URI || process.env.MLAB_URL;
database.once('open', () => {
//Init Stream
gfs = Grid(database.db, mongoose.mongo);
gfs.collection('uploads');
});
//Create Storage Engine
const storage = new GridFsStorage({
url: mongoDb,
file: (res, file) => {
return new Promise((resolve, reject) => {
crypto.randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = buf.toString('hex') + path.extname(file.originalname);
const fileInfo = {
filename: filename,
bucketName: 'uploads',
metadata: 'NEED TO UPDATE THIS'
};
resolve(fileInfo);
});
});
}
});
const uploadEngine = multer({ storage });
module.exports = {
uploadEngine,
gfs
};
Above you can see the metadata property that I need to be able to dynamically change with some undetermined unique identifier. Is it possible to do that with an exported file?
Here is how I am utilizing it inside of an API route:
const express = require('express');
const router = express.Router();
//Controllers
const upload_controller = require('../../controllers/uploader');
//Utilities
const upload = require('../../utils/gridFs_upload_engine');
const { uploadEngine } = upload;
//Upload Single File
router.post(
'/single',
uploadEngine.single('file'),
upload_controller.upload_single_file
);
//Upload Multiple Files
//Max file uploads at once set to 30
router.post(
'/multiple',
uploadEngine.array('file', 30),
upload_controller.upload_multiple_files
);
module.exports = router;
I pass the uploadEngine into the API route here, so that the route controller can use it, and that works with no issue. I am just having quite a time trying to figure out how to update metatdata dynamically and I am leaning towards my current implementation not allowing for that.
I don't know much about node and have now idea what multer-gridfs is but I can answer How can I pass options into an imported module?
You can export an function that returns another function. And you would import it like
const configFunction = require('nameoffile')
// this returns a functions with the config you want
const doSomethingDependingOnTheConfig = configFunction({...someConfig})
And in the file you are importing you would have a function returning another function like
const configFunction = ({...someConfig}) => (your, func) => {
// do what you want deppending on the config
}
module.exports = configFunction
I know this doesn't answer your question the way you want, but answer you question title and I hope this give you a better understanding of how to do what you want to do.
If this doesn't help, just let me know.
You would need to pass a parameter to the module gridFs_upload_engine.js and do the magic there.
An example could be:
In gridFs_upload_engine.js file:
function uploadEngine (id, file) {
// update what you want
}
module.exports = {
...
uploadEngine: uploadEngine
}
In your router:
const upload = require('../../utils/gridFs_upload_engine')
...
router.post('/single/:id', function(req, res, next) {
...
upload.uploadEngine(req.params.id, file)
next()
}, upload_controller.upload_single_file)
In other words, when you are exposing gfs and uploadEngine inside your module, you could instead expose a function that would receive the arguments needed to perform the upload.

how to attach middleware in swagger express like express-validator

I am trying to edit my express-swagger project and following this tutorial to add input validation into an express app
I used swagger-express-mw package to generate a boilerplate using swagger project create app but its not clear where I can add my middlewares as explained in the tutorial I mentioned above. Specifically i cant intercept my request, I get a typeError:
TypeError: req.checkBody is not a function
at saveNote
Here is my entry file. Everything apart from the bodyParser and validator function is coming from the boilerplate so I dont understand
SwaggerExpress.create(config, function(err, swaggerExpress) {
if (err) { throw err }
// install middleware
swaggerExpress.register(app)
// middleware
app.use(bodyParser.urlencoded({ extended: false })); // my code
app.use(validator()) // my d
const port = process.env.PORT || 10010
app.listen(port)
// if (swaggerExpress.runner.swagger.paths['/notes']) {
// console.log('try this:\nlocalhost:10010/notes to get all notes')
// }
})
I know this is an old question, but this might help someone along the way.Here is the simple NodeJS code showing how to integrate swagger-tools with swagger-express-mw or swagger-node-runner npm packages.
var SwaggerExpress = require('swagger-express-mw');
var swaggerTools = require('swagger-tools');
var app = require('express')();
module.exports = app; // for testing
var config = {
appRoot: __dirname // required config
};
SwaggerExpress.create(config, function(err, swaggerExpress) {
if (err) {
throw err;
}
// install middleware
swaggerExpress.register(app);
//swaggerobject is created by the swagger-express-mw module which is stored in runner object.
var swaggerObjectLoaded = swaggerExpress.runner.swagger;
//pass the runner object to initializeMiddleware function
swaggerTools.initializeMiddleware(swaggerObjectLoaded, function(middleware) {
app.use(middleware.swaggerMetadata());
// Validate Swagger requests
app.use(middleware.swaggerValidator());
// Route validated requests to appropriate controller
app.use(middleware.swaggerRouter(options));
// Serve the Swagger documents and Swagger UI
app.use(middleware.swaggerUi());
var port = process.env.PORT || 10010;
app.listen(port);
if (swaggerExpress.runner.swagger.paths['/hello']) {
console.log('try this:\ncurl http://127.0.0.1:' + port + '/hello?name=Scott');
}
});
});

Mocha API Testing: getting 'TypeError: app.address is not a function'

My Issue
I've coded a very simple CRUD API and I've started recently coding also some tests using chai and chai-http but I'm having an issue when running my tests with $ mocha.
When I run the tests I get the following error on the shell:
TypeError: app.address is not a function
My Code
Here is a sample of one of my tests (/tests/server-test.js):
var chai = require('chai');
var mongoose = require('mongoose');
var chaiHttp = require('chai-http');
var server = require('../server/app'); // my express app
var should = chai.should();
var testUtils = require('./test-utils');
chai.use(chaiHttp);
describe('API Tests', function() {
before(function() {
mongoose.createConnection('mongodb://localhost/bot-test', myOptionsObj);
});
beforeEach(function(done) {
// I do stuff like populating db
});
afterEach(function(done) {
// I do stuff like deleting populated db
});
after(function() {
mongoose.connection.close();
});
describe('Boxes', function() {
it.only('should list ALL boxes on /boxes GET', function(done) {
chai.request(server)
.get('/api/boxes')
.end(function(err, res){
res.should.have.status(200);
done();
});
});
// the rest of the tests would continue here...
});
});
And my express app files (/server/app.js):
var mongoose = require('mongoose');
var express = require('express');
var api = require('./routes/api.js');
var app = express();
mongoose.connect('mongodb://localhost/db-dev', myOptionsObj);
// application configuration
require('./config/express')(app);
// routing set up
app.use('/api', api);
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('App listening at http://%s:%s', host, port);
});
and (/server/routes/api.js):
var express = require('express');
var boxController = require('../modules/box/controller');
var thingController = require('../modules/thing/controller');
var router = express.Router();
// API routing
router.get('/boxes', boxController.getAll);
// etc.
module.exports = router;
Extra notes
I've tried logging out the server variable in the /tests/server-test.js file before running the tests:
...
var server = require('../server/app'); // my express app
...
console.log('server: ', server);
...
and I the result of that is an empty object: server: {}.
You don't export anything in your app module. Try adding this to your app.js file:
module.exports = server
It's important to export the http.Server object returned by app.listen(3000) instead of just the function app, otherwise you will get TypeError: app.address is not a function.
Example:
index.js
const koa = require('koa');
const app = new koa();
module.exports = app.listen(3000);
index.spec.js
const request = require('supertest');
const app = require('./index.js');
describe('User Registration', () => {
const agent = request.agent(app);
it('should ...', () => {
This may also help, and satisfies #dman point of changing application code to fit a test.
make your request to the localhost and port as needed
chai.request('http://localhost:5000')
instead of
chai.request(server)
this fixed the same error message I had using Koa JS (v2) and ava js.
The answers above correctly address the issue: supertest wants an http.Server to work on. However, calling app.listen() to get a server will also start a listening server, this is bad practice and unnecessary.
You can get around by this by using http.createServer():
import * as http from 'http';
import * as supertest from 'supertest';
import * as test from 'tape';
import * as Koa from 'koa';
const app = new Koa();
# add some routes here
const apptest = supertest(http.createServer(app.callback()));
test('GET /healthcheck', (t) => {
apptest.get('/healthcheck')
.expect(200)
.expect(res => {
t.equal(res.text, 'Ok');
})
.end(t.end.bind(t));
});
Just in case, if someone uses Hapijs the issue still occurs, because it does not use Express.js, thus address() function does not exist.
TypeError: app.address is not a function
at serverAddress (node_modules/chai-http/lib/request.js:282:18)
The workaround to make it work
// this makes the server to start up
let server = require('../../server')
// pass this instead of server to avoid error
const API = 'http://localhost:3000'
describe('/GET token ', () => {
it('JWT token', (done) => {
chai.request(API)
.get('/api/token?....')
.end((err, res) => {
res.should.have.status(200)
res.body.should.be.a('object')
res.body.should.have.property('token')
done()
})
})
})
Export app at the end of the main API file like index.js.
module.exports = app;
We had the same issue when we run mocha using ts-node in our node + typescript serverless project.
Our tsconfig.json had "sourceMap": true . So generated, .js and .js.map files cause some funny transpiling issues (similar to this). When we run mocha runner using ts-node. So, I will set to sourceMap flag to false and deleted all .js and .js.map file in our src directory. Then the issue is gone.
If you have already generated files in your src folder, commands below would be really helpful.
find src -name ".js.map" -exec rm {} \;
find src -name ".js" -exec rm {} \;
I am using Jest and Supertest, but was receiving the same error. It was because my server takes time to setup (it is async to setup db, read config, etc). I needed to use Jest's beforeAll helper to allow the async setup to run. I also needed to refactor my server to separate listening, and instead use #Whyhankee's suggestion to create the test's server.
index.js
export async function createServer() {
//setup db, server,config, middleware
return express();
}
async function startServer(){
let app = await createServer();
await app.listen({ port: 4000 });
console.log("Server has started!");
}
if(process.env.NODE_ENV ==="dev") startServer();
test.ts
import {createServer as createMyAppServer} from '#index';
import { test, expect, beforeAll } from '#jest/globals'
const supertest = require("supertest");
import * as http from 'http';
let request :any;
beforeAll(async ()=>{
request = supertest(http.createServer(await createMyAppServer()));
})
test("fetch users", async (done: any) => {
request
.post("/graphql")
.send({
query: "{ getQueryFromGqlServer (id:1) { id} }",
})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end(function (err: any, res: any) {
if (err) return done(err);
expect(res.body).toBeInstanceOf(Object);
let serverErrors = JSON.parse(res.text)['errors'];
expect(serverErrors.length).toEqual(0);
expect(res.body.data.id).toEqual(1);
done();
});
});
Edit:
I also had errors when using data.foreach(async()=>..., should have use for(let x of... in my tests

NodeJs : modules.export : Cannot find one of my own custom module

I am trying to include my own users.js module to my router file. It keeps throwing the error:
Cannot find module './router/users.js'
My directory structure is as follows:
nodejs (Main folder on my drive)
-- expressserver.js (My server file)
-- package.json
-- router (folder containing main.js router and users.js user details file)
----- main.js
----- users.js
----- orders.js
Here my users module is in the same folder as my router (main.js)
My code for router is:
var url = require('url');
var users = require('./router/users.js');
module.exports = function (app) {
app.get('/', function (req, res) {
res.render('index.html');
console.log("Home page displayed");
});
app.get('/login', function (req, res) {
res.render('login.html');
console.log("Log in page displayed");
});
app.get('/api/orders/:id', function (req, res) {
console.log(req.params.id);
res.json(ORDER.orders[req.params.id]);
});
app.get('/api/orders/', function (req, res) {
console.log(ORDER);
res.json(ORDER);
});
app.get('/api/users/:id', function (req, res) {
console.log(req.params.id);
res.json(USERS.users[req.params.id]);
});
app.get('/api/users/', function (req, res) {
console.log(USERS);
res.json(USERS);
});
My code for users.js:
module.exports = function () {
// Create User prototype and objects
var USERS = { users: [] };
function User(type, useremail, password) {
this.type = type;
this.useremail = useremail;
this.password = password;
}
var Bob = new User("rep", "bob#bob.com", "qwerty");
USERS.users.push(Bob);
var Helen = new User("rep", "helen#helen.com", "test");
USERS.users.push(Helen);
var Dominic = new User("customer", "dom#dom.com", "1234");
USERS.users.push(Dominic);
var James = new User("grower", "james#james.com", "pass1");
USERS.users.push(James);
};
I'm pretty new to this are but have been reading up alot on modules but still can't figure it out. Any suggestions on where I have gone wrong? or what I need to do to rectify the problem?
Note: Previously I did the same thing for including router into the server file using module.exports = function (app) { around my router and in my server as: require('./router/main')(app);
Since you have specified a relative path to the module, that path is relative to the directory of the source file where require is performed. In your case, it is already relative to the 'router' directory. This will work:
var users = require('./users.js');
Or even just the following, since the extension is automatically resolved:
var users = require('./users');
The path for require(), is set for node_modules folder by default.
That is why you are able to require all modules such as var url = require('url'); in your case.
If the module is not found there in your current project, the module will be searched globally(if there are path variables set on machine).
Now when you define custom modules , you can either keep them within the folder node_modules, OR you can give a path relative to your current JS file within which you are requiring the module.
For example,
var users = require('./users');
If there is another folder, in your current working directory, say modules,
then you can do require it like this:
router
----- main.js
----- orders.js
----------------modules(folder)
-----------------------users.js
var users = require('./modules/users');
So the path for require always starts from node_modules folder

Categories

Resources