[ original gist 12 July 2013 ]
Instead of trying to extract a private function, we can rely on mocking/spying. This gist post shows how to use the Function()
constructor to replace an internal call so that we can mock, spy or stub.
First Things
This is a followup to my (second) response at https://gist.github.com/dfkaye/5971486 to @philwalton, a suggestion for annotating functions to be extracted and tested separately (publicly). @philwalton’s original post, How to Unit Test Private Functions in JavaScript, shows how to use annotations around JavaScript fragments that expose otherwise private data publicly, but which are removed by a build process.
@philwalton very patiently listened to an unexpectedly opinionated JavaScript readership (who knew?) – for that I applaud him. I recommend you read his work regarding HTML Inspector as well as Object-oriented CSS, both of which I heartily endorse.
The mocking-not-testing alternative
If a function is public and uses another private function internally, we need a way to watch the public function’s behavior based on the result of its call to the private function.
How to do that using Function()
Starting with this:
var myModule = (function() {
function foo() {
// private function `foo` inside closure
return "foo"
}
var api = {
bar: function() {
// public function `bar` returned from closure
return foo() + "bar"
}
}
return api
}())
Steps we’ll take:
- create a mock
foo
function to be called in place of the realfoo
function - copy the source of the
bar
function under test - overwrite the inner function call
foo()
with"mockFoo()"
inside thebar()
function that we’re testing - create a new
Function
from the modifiedbar
source - exercise the new
bar
function under test:
Details
Create a mock for foo
:
function mockFoo() {
return 'mockfoo'
}
Copy the source of bar
:
var fn = myModule.bar.toString();
Overwrite the foo()
call with mockFoo()
in the new bar source text:
fn = fn.replace('foo', 'mockFoo');
Create a new Function
from the modified bar
source
// need to remove the open and close braces from the function source
fn = fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}') - 1)
// create the new bar function
var bar = new Function(fn)
Exercise the new function under test:
assert(bar() === 'mockfoobar');
Closing statements
You could reassign the new bar function as myModule.bar
directly, in the case there’s any this.something
referencing going on.
This approach doesn’t test the private function directly, but does test the public function that relies on it. Therefore, it may help to add some test logic to the original bar
function to verify that foo
returns expected values, and that bar
reacts as expected when foo
returns something unexpected.
Metafunction
[ 27 Aug 2015 ] This line of thought developed into my own metafunction
project on github.