Often I have functions that need data from either a config file or a database. I would prefer pure functions at much as possible and something that can be unit tested.
I have no idea how to do this correctly, as I don't have a CS degree. The options I can see are:
Example 1:
function myFunc(p1, p2, db, configFile) { ... }
but that means all other functions that calls myFunc()
also needs to have db, configFile
as arguments.
Example 2:
function myFunc(p1, p2) {
const config = readConfig();
dbConn = openDb();
...
}
I suppose this make unit testing impossible?
Example 3:
const struct = {
config: readConfig(),
dbConn: openDb(),
...
};
function myFunc(p1, p2, struct) { ... }
This have the down side, that when other functions data also gets stored in this global struct
then each function have access to more than what they need.
Question
Are there other possibilities? What is the recommended approach?
CodePudding user response:
Define myFunc()
like so to keep it pure:
function myFunc(p1, p2, readConfig, openDb) { ... }
and call it in an enclosed scope
// other code here
{
const config = readConfig();
const dbConn = openDb();
myFunc(p1, p2, readConfig, openDb);
}
// other code here
// config and dbConn isn't available here
CodePudding user response:
I think you can use Dependency Injection here. Something like this:
function readConfig() {
// returns real config
}
function openDb() {
// return real db connection
}
function readConfigMock() {
// returns mock config
}
function openDbMock() {
// return mock db connection
}
function myFuncBuilder(readConfigFn, openDbFn) {
return function(p1, p2) {
// can use readConfigFn everywhere because of closure
const config = readConfigFn();
dbConn = openDbFn();
...
}
}
const myFunc = myFuncBuilder(readConfig, openDb);
// no need to know about openDb and readConfig
function topFunc() {
myFunc(p1, p2);
}
// code
const myFunc = myFuncBuilder(readConfig, openDb);
// test
const myFunc = myFuncBuilder(readConfigMock, openDbMock);
The trick is passing a function as an argument. Also, there is higher-order function (function that returns function) called myFuncBuilder
. This function builds myFunc
based on 2 arguments - function for db connection and function for config.
So you can easily create a mocks of readConfig
and openDb
for testing.
P.S. Also, Functional Programming paradygm teaches us to use IO monads in such cases. But using monads in JS/TS has a really steep learning curve.