An overview of design practices for building modules/applications to best facilitate unit testing.
The primary goal of the unit test is to be able to exercise as much functionality as possible within a certain module (unit). The best way to achieve this is to completely separate functionality into units (or modules) with any external dependencies being injected at initialization.
A user controller should contain only user validation. User objects need to be persisted to a data store; however, database functionality is a dependency that should be injected at initialization time.
The user controller unit tests must be limited to only the functionality of the user controller. Once a database call is made, the process is out of scope of the unit test (these pieces are either tested in the database unit test or not at all in the case of external libraries). Injecting the database controller as a dependency allows the database controller to be mocked prior to being injected, correctly separating the concerns of both the user controller logic and the user controller tests.
Suggested structure:
|-- server.js (main script)
|-- controllers
| |-- index.js (bootstrap script for controllers)
| |-- user_controller.js
| `-- project_controller.js
|-- models (database functionality - could use a better name)
| |-- index.js (bootstrap script for models)
| |-- user_model.js
| `-- project_model.js
`-- routes
|-- index.js (bootstrap script for routes)
|-- user_routes.js
`-- project_routes.js
Server.js initializes the express application and attaches all of the pieces - it depends on routes which depend on controllers, which depend on models:
var express = require('express')
, app = express()
, controllers = require('./controllers')
, models = require('./models')
, routes = require('./routes');
//
// Initialize express middleware...
//
models.init();
controllers.init(models);
routes.init(app, controllers);
app.listen(process.env.PORT || 8888);The bootstrap scripts simply initialize each of the modules with the dependencies that are passed into the init function which means that dependencies are controlled by the script that initializes the module.. in other words, a unit test can now pass mocks for the dependencies.