Two of the easiest possible ways of doing this are as follows:
1) Admit that controllers can not be unit tested, and because they can not be unit tested, they will be a part of your integration tests, and because that is true, you are going to move as much unit testable code out of them as physically possible, and limit them to doing little more than composing pieces of code which are able to be tested. This would be fine; an Express controller is sort of like a main
method into your application, from the view of an individual request, anyway, so having it mix together the hard concepts to decide what gets built is okay, as long as the main
isn't doing the heavy lifting, but rather instantiating and running things which are doing the lifting.
2) Compose your functions so that they all receive all of the props that they require, to do their jobs, and moreso, they nearly always receive them from other files (not the file they're defined in), unless the assignment is just as a default value.
In the case of a controller, if you wanted the controller to be unit-testable, you would preconfigure it to accept a ProductModel
to use (or a function to use) rather than assuming you are going to pull in the real model.
export const CreateProduct = (ProductModel) => async (req, res, next) => {
const productData = req.body
try {
const product = await ProductModel.create(productData)
res.send(product)
} catch (err) {
next(err)
}
}
import { CreateProduct } from './create_product'
import { ConfigureProductModel } from './somewhere_else'
export const ProductRouter = express.Router()
ProductRouter.post('/', CreateProduct(ConfigureProductModel(database)))
Now to test that, you can easily create a CreateProduct
where you pass in a fake ProductModel
. And if you use the example, there of ConfigureProductModel
as a factory, you can test it by passing it a fake db instance (if you do, indeed have that level of control).
And personally, like I said, as a controller, I want to remove as much control as possible, so I'd probably take most of the imperative code away.
const CreateProduct = ProductModel => (req, res, next) =>
ProductModel.create(req.body)
.then(product => res.send(product))
.catch(next);