Stubbing variables in a constructor? - javascript

I'm trying to figure out how to properly stub this scenario, but i'm a little stuck.
The scenario is, i've got a db.js file that has a list of couchdb databases in it (each database contains tweet entries for a particular year).
Each year a new database is created and added to this list to hold the new entries for that year (so the list of databases isn't constant, it changes each year).
So my db.js file looks like this:
var nano = require('nano')(`http://${host}`);
var databaseList = {
db1: nano.use('db2012'),
db2: nano.use('db2013'),
db4: nano.use('db2014'),
db5: nano.use('db2015'),
db6: nano.use('db2016')
};
module.exports.connection = nano;
module.exports.databaseList = databaseList;
And event.js (a simple model file), before methods are added looks like this:
var lastInObject = require('../../helpers/last_in_object');
var db = require('../../db');
var EventModel = function EventModel() {
this.connection = db.connection;
this.databaseList = db.databaseList;
this.defaultDatabase = lastInObject(db.databaseList);
};
EventModel.prototype.findAll =
function findAll(db, callback) {/* ... */}
My question is, how do i stub the databaseList, so i can safely test each of the model methods without having any brittleness from the growing databaseList object?
Ideally i'd like to be able hijack the contents of the databaseList in my tests, to mock different scenarios, but i'm unsure how to tackle it.
Here's an example test, to ensure the defaultDatabase property is always pointing to the last known event, but obviously i don't want to have to update this test every year, when databaseList changes, as that makes the tests very brittle.
it('should set the default database to the last known event', () => {
var Event = require('../event');
var newEventModel = new Event();
expect(newEventModel.defaultDatabase.config.db)
.to.equal('db2014');
});
Suggestions welcome! If i've gone about this wrong, let me know what i've done and how i can approach it!
Also, this is just a scenario, i do have tests for lastInObject, i'm more interested in how to mock the concerning data.

In my opinion you need to stub the whole "db" module. That way you won't have any real db connection and you can easily control the environment of your tests. You can achieve this by using the mockery module.
That way you can stub the object that the require('../../db') returns. This will allow you to set whatever value you like in the properties of that object.

Building upon Scotty's comment in the accepted answer a bit... here's some code I've got which seems to do the job. No guarantees that this is a good implementation, but it does successfully stub out the insert and get methods. Hopefully it helps someone. :)
// /src/common/database.js
const database = require('nano')('http://127.0.0.1/database');
module.exports = {
async write(document, documentId) {
return await new Promise(resolve => database.insert(document, documentId, resolve));
},
async read(documentId){
return await new Promise(resolve => database.get(documentId, resolve));
}
};
// /test/setup.js
const proxyquire = require('proxyquire');
proxyquire('../src/common/database.js', {
nano() {
return {
insert(document, documentId, callback){ callback(); },
get(documentId, callback){ callback(); }
};
}
});

Related

What's the advantage of fastify-plugin over a normal function call?

This answer to a similar question does a great job at explaining how fastify-plugin works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register() method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
By writing a decorateApp function you are creating your own "API" to load your application.
That said, the first burden you will face soon is sync or async:
decorateApp is a sync function
decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('#fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
the decorateApp function must be async
the mongodb registration must be awaited
the main code that loads the application must be async
Instead, by using the fastify's approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('#fastify/mongodb'))
- next()
+ await app.register(require('#fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the next callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse will need to know that it is loaded after decorateAppWithConfig.
Instead, by using the fastify-plugin, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined when the config decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin adds many validation checks to it.
So, by considering the question's example: there is no difference, but using that approach to a bigger application
will lead to a more complex code:
sync/async loading functions
poor error messages
hidden dependencies instead of explicit ones

Cant read data from collection in MongoDB Atlas Trigger

New to MongoDB, very new to Atlas. I'm trying to set up a trigger such that it reads all the data from a collection named Config. This is my attempt:
exports = function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
config_docs = collection.find().toArray();
console.log(JSON.stringify(config_docs));
}
the function is part of an automatically created realm application called Triggers_RealmApp, which has Cluster0 as a named linked data source. When I go into Collections in Cluster0, TestDB.Config is one of the collections.
Some notes:
it's not throwing an error, but simply returning {}.
When I change context.services.get("Cluster0"); to something else, it throws an error
When I change "TestDB" to a db that doesnt exist, or "Config" to a collection which doesn't exist, I get the same output; {}
I've tried creating new Realm apps, manually creating services, creating new databases and new collections, etc. I keep bumping into the same issue.
The mongo docs reference promises and awaits, which I haven't seen in any examples (link). I tried experimenting with that a bit and got nowhere. From what I can tell, what I've already done is the typical way of doing it.
Images:
Collection:
Linked Data Source:
I ended up taking it up with MongoDB directly, .find() is asynchronous and I was handling it incorrectly. Here is the reply straight from the horses mouth:
As I understand it, you are not getting your expected results from the query you posted above. I know it can be confusing when you are just starting out with a new technology and can't get something to work!
The issue is that the collection.find() function is an asynchronous function. That means it sends out the request but does not wait for the reply before continuing. Instead, it returns a Promise, which is an object that describes the current status of the operation. Since a Promise really isn't an array, your statment collection.find().toArray() is returning an empty object. You write this empty object to the console.log and end your function, probably before the asynchronous call even returns with your data.
There are a couple of ways to deal with this. The first is to make your function an async function and use the await operator to tell your function to wait for the collection.find() function to return before continuing.
exports = async function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
config_docs = await collection.find().toArray();
console.log(JSON.stringify(config_docs));
};
Notice the async keyword on the first line, and the await keyword on the second to last line.
The second method is to use the .then function to process the results when they return:
exports = function(changeEvent) {
const mongodb = context.services.get("Cluster0");
const db = mongodb.db("TestDB");
var collection = db.collection("Config");
collection.find().toArray().then(config_docs => {
console.log(JSON.stringify(config_docs));
});
};
The connection has to be a connection to the primary replica set and the user log in credentials are of a admin level user (needs to have a permission of cluster admin)

How do I spy on an injected constructor?

I have a Manager that makes heavy use of the serialport module, and would like to run unit tests with a mocked-up version of serialport. I've rewritten my manager module to exploit dependency injection:
// file: manager.js
function Manager(SerialPort, device, baudRate) {
this._device = device;
this._serialPort = new SerialPort(device, {autoOpen: false, baudRate: baudRate })
};
Manager.prototype.constructor = Manager;
Now I'd like to test to see that the correct arguments are passed to the new SerialPort() call. I'm stuck on how to properly stub/mock the constructor. Here's what I have so far:
// file: manager-test.js
function MockSerialPort(device, options) {}
describe('Manager', function() {
describe('constructor', function() {
it('instantiates a serial port with correct baud rate', function() {
const manager = new Manager(MockSerialPort, '/dev/tty', 9600);
expect(<something>).to.be.calledWith('/dev/tty', {autoOpen: false, baudRate: 9600});
});
});
});
Clearly I have a couple of holes
in my head, oops, no I mean
in my code. What do I need to fill out this test?
update
As #cdhowie points out, life is easier if I can pass an instantiated SerialPort object into Manager. Due to some misleading documentation, I didn't think that was possible with the SerialPort API, but thanks to his help, the implementation now looks like:
function Manager(serialPort) {
this._serialPort = serialPort;
}
Manager.prototype.constructor = Manager;
This means that in my tests, I simply create something that looks, swims and quacks like a SerialPort object. Stubbing and spying then becomes trivial.
Just use an anonymous function that stores its arguments in the scope of the test:
let ctorArgs;
const manager = new Manager(function () {
ctorArgs = Array.prototype.slice.call(arguments);
}, '/dev/tty', 9600);
// Assert against the contents of ctorArgs
Side note: why do you pass the constructor function and arguments to Manager instead of just passing a constructed SerialPort object? Unless Manager needs to create more than one object with the same arguments, it seems a bit silly to give it the burden of creating the object.

How to organize models in a node.js website?

I have just started experimenting with building a website using node.js, and I am encountering an issue when organizing the models of my project.
All the real world examples I have found on the Internet are using Mongoose. This library allows you to define your models in a static way. So you can write this:
// models/foo.js
module.exports = require('mongoose').model('Foo', ...);
// app.js
mongoose.connect(...);
// some_controller_1.js
var Foo = require('./models/foo');
Foo.find(...);
// some_controller_2.js
var Foo = require('./models/foo');
Foo.find(...);
But since I don't want to use MongoDB, I need another ORM. And all the other ORMs I have found don't allow this. You first need to create an instance, and then only you can register your models. Also they don't seem to allow access to the list of registered models.
So I tried doing this:
// models/user.js
var registrations = [];
module.exports = function(sequelize) {
var result = null;
registrations.forEach(function(elem) {
if (elem.db == sequelize)
result = elem.value;
});
if (result) return result;
// data definition
var user = sequelize.define("User", ...);
registrations.push({ db: sequelize, value: user });
return user;
};
Which I can use this like:
// some_controller_1.js
var Foo = require('./models/foo')(app.get('database'));
Foo.find(...); // using Foo
But these small header and footer that I have to write on every single model file are a bit annoying and directly violate the "don't repeat youself" principle. Also, while not a huge issue, this is kind of a memory leak since the "sequelize" object will never be freed.
Is there a better way to do, which I didn't think about?
You can find an article about handling models with sequelize here: http://sequelizejs.com/articles/express#the-application
You basically just create a models/index.js as described here: http://sequelizejs.com/articles/express#block-3-line-0
Afterwards you just put your model definitions within files in the models folder as pointed out here: http://sequelizejs.com/articles/express#block-4-line-0

Structure for unit testing on node.js with mongoose

I've been developing with node.js for months but now I'm starting a new project and I'd like to know how to structure the app.
My problem comes when talking about unit testing. I will use nodeunit to write unit tests.
Also I'm using express to define my REST routes.
I was thinking about writing my code that access databases in two "separate" files (They will be more, obviously, but I'm just trying to simplify the code). There will be the routes code.
var mongoose = require('mongoose')
, itemsService = require('./../../lib/services/items-service');
// GET '/items'
exports.list = function(req, res) {
itemsService.findAll({
start: req.query.start,
size: req.query.size,
cb: function(offers) {
res.json(offers);
}
});
};
And, as I'm using there, an item service used just to access data layer. I'm doing this to test only data access layer on unit testing. It'll be something like this:
var mongoose = require('mongoose')
, Item = require('./../mongoose-models').Item;
exports.findAll = function(options) {
var query = Offer
.find({});
if (options.start && options.size) {
query
.limit(size)
.skip(start)
}
query.exec(function(err, offers) {
if (!err) {
options.cb(offers);
}
})
};
This way I can check with unit testing if it works correctly and I can use this code everywhere I want. The only thing I'm not sure if it's been correctly done is the way I pass a callback function to use returned value.
What do you think?
Thanks!
Yes, quite easily!
You can use a unit testing module like mocha and either node's own assert or another such as should.
As an example of a test case for your example model:
var ItemService = require('../../lib/services/items-service');
var should = require('should');
var mongoose = require('mongoose');
// We need a database connection
mongoose.connect('mongodb://localhost/project-db-test');
// Now we write specs using the mocha BDD api
describe('ItemService', function() {
describe('#findAll( options )', function() {
it('"args.size" returns the correct length', function( done ) { // Async test, the lone argument is the complete callback
var _size = Math.round(Math.random() * 420));
ItemService.findAll({
size : _size,
cb : function( result ) {
should.exist(result);
result.length.should.equal(_size);
// etc.
done(); // We call async test complete method
}
},
});
it('does something else...', function() {
});
});
});
And so on, ad nauseum.
Then when you're done writing your tests - assuming you've $ npm install mocha'd - then you'd simply run $ ./node_modules/.bin/mocha or $ mocha if you used npm's -g flag.
Depends how rectal/detailed you want to be really. I've always been advised to, and find it easier to: Write the tests first, to get a clear specification perspective. Then write the implementation against the tests, with any extra insight a freebie.

Categories

Resources