Problem
Sometimes we need to test hidden (closure) functions or an interface by isolating the use of hidden functions. It is also sometimes necessary to set the initial state of the hidden variables, or vice versa to consider their state after the test. In this case, we build an additional set of functions that provides access inside the object. Often this set is very cumbersome and is intended only to ensure the testability of the module.
But there is an easy way to avoid writing this code.
Lyrical digression
I am new to JavaScript and Unit Tests. Perhaps this solution is already being used with might and main, but I found it myself and I hasten to share it with others.
')
Decision
I found out that the well-known (and hated by many) function
eval()
executes the given code in the lexicological context of its call.
What does this give us?
Instead of a complex API, we can add just one function that does all the work.
this.evalInContext = function (cmd) {eval (cmd);}
Example
The object might look like this:
function factory(params){ var outerVar; function outerFunc(){} function MyClass(params2){ var innerVar; function innerFunc(){} this.instanceVar = 0; this.instanceFunc(){} this.evalInContext = function(cmd){eval(cmd);} } return new Myclass(params); }
With this use, all arguments, as well as external, internal and object functions and variables will be available to the code passed to the
eval ()
function.
Using
var myObj = factory();
Extensions
To set simple values in variables or to call a function with simple parameters, you can use the direct call to
evalInContext()
. But what to do if we need to work with more complex objects.
I created some simple functions to extend the basic functionality of
evalInContext()
.
Setting and getting complex variables
This is what setter looks like:
function setVar(obj, name, value){ obj.evalInContext("name + " = arguments[1]", value); }
We pass the value as a parameter to the
evalInContext()
function and use
arguments
to assign data. This way you can transfer any type of data.
Getter works in a similar way, but uses another simple trick:
function getVar(obj, name){ var res = {}; obj.evalInContext("arguments[1]['" + name + "'] = " + name + ";", res); return res[name]; }
We pass an empty object as a container for the return value, since
return
cannot be used in the
eval()
function.
UPD : In the comments offered a more
interesting option . And for me more correct.
Function substitution (mocking)
Since the name of the function is a variable, we can use the existing functionality:
function replaceFunction(obj, name, replacement) { var orig = getVar(obj, name); setVar(obj, name, replacement); return orig; }
Calling a function with complex parameters
We could make a wrapper to call a function, but it's easier to get the function itself and call it in the usual way. In addition, having received it once you can call as much as you like.
To get the function, you can use the already available getter.
Underwater rocks
Of course this solution is not perfect. For example, refactoring can easily stumble over the use of the names of internal variables and functions in strings and even outside the module. This can seriously complicate test support.
In addition, this function must be removed from the final code, since it completely destroys all the concepts of code security.