Mocking - not testing - private functions in JavaScript
Instead of trying to extract a private function, we can rely on mocking/spying. This gist shows how to use the "new Function()" constructor to replace an internal call so that we can mock, spy or stub.
Another response to @philwalton -
http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/
This is a followup to https://gist.github.com/dfkaye/5971486 - a suggestion for
*annotating* functions to be extracted and tested separately (publicly).
My first response - https://gist.github.com/dfkaye/5959089 - outlines testing a private
function in a greenfield situation, before its embedding in a closure to be used by a
second public function.
First Things
@philwalton has 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 new Function()
- create a mock foo function to be called in place of the real foo function
- copy the source of the bar function under test
- overwrite the inner function call foo() with "mockFoo()" inside the bar() function that we're testing
- create a new Function from the modified bar source
- exercise the new bar function under test:
Details
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
}())
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.
A complete solution to this problem here => https://github.com/dfkaye/metafunction