We’re unit testing a module in Node. That unit has dependencies on some other sub-unit. For our example, these units are modules. The subject under test is
prep-for-fight.js. It has a dependency on
eat-corn.js. We want to eventually stub out
eat-corn.js within our unit test for
1 2 3 4 5 6 7
A stub is a testing fake that you create in place of the real thing. While testing our module
prepForFight, we’ll stub out the sub-unit module
eatCorn. That module is tested elsewhere in another unit test. We don’t want to conflate the two tests, coupling them strongly together. This is because if the sub-unit’s implementation changes, we don’t want to have to change our current unit’s test.
The tradeoff is that we are going to couple our
prepForFight unit test to the implementation of that module. This is generally known as white box testing, where we care about the internal implementation of our source code within our tests. If we did the opposite, and treated
prepForFight as a black box and just tested its final output, there really is no need to stub anything or ever care about implementation as long as
prepForFight continues to do its job. For today’s example, we’ll choose to stub so that we can have a more isolated unit and focused test, drawing our unit boundaries strictly around code that exists in
There are libraries that help us stub. Since we have a
required module for
eat-corn.js, there is one in particular that would do well for us called proxyquire. It allows targeting the
./eat-corn.js import path and replacing it with your own module at test runtime. With a couple caveats, it usually works quite well. We’re going to not use it and see how we fare.
Stub by Passing the Dependency
An easy way to get a dependency into
prepForFight is to pass it as a function argument. The rewrite might look like this:
1 2 3 4 5 6
Now we have no
require statement, and
prepForFight gets the dependency it needs. Given this implementation, we can exercise our two code paths in our test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
By passing in our stub directly, we control the branching inside the function.
eatCorn as a function parameter, we’re telling all consumers that we rely on
eatCorn. We’ve leaked our dependency, lessening our encapsulation. For the function to work as written, it always needs the consumer to send it the
eatCorn argument when
prepForFight is called. Let’s give it a default, and make the consumer code care about our dependencies a bit less. The default will be our original
1 2 3 4 5 6 7
eatCorn is passed as an argument, it will be used. Otherwise,
defaultEatCorn, which is the normal imported dependency, will be used. This is great because now consumers don’t necessarily have to care about the dependency, except to override, which for now is just a thing our test wants to be able to do.
Stub Without Changing Your Signature
eatCorn in your function parameter list bothers you, here’s another potential solution.
1 2 3 4 5 6 7 8 9 10 11
Now you can call
withEatCornForTest before you exercise your subject under test:
1 2 3 4 5 6 7 8 9 10 11
This overrides the imported
eatCorn module much like proxyquire does. This is nice because your
prepForFight function remains untouched, but I think there are a few drawbacks.
- We had to change our single export module to a multiple named export module in order to add the extra API for setting the dependency.
- We have code in our src that is there specifically for testing. The
*ForTestsuffix is a particularly clear flag of that. But if we remove the
*ForTestsuffix, we simply cloud that fact and make something still test-specific look like it’s for general use.
- We have made our
prepForFightfunction impure, because now its output can change depending on when or if we call the
withEatCornForTest, creating a module-global side effect.
withEatCornForTestis further away from the
eatCornusage inside of
prepForFight. Thus, we could read
prepForFightand never know that it’s possible for the
eatCornimplementation to be switched out from under us without examination of more code outside that function.
Decouple Function Parameter Order
Previous to reading Sandi Metz’ POODR book, I hadn’t considered this, but she posits that a parameter list has coupling because of the order of the parameters. To lessen the coupling, she proposes changing the function signature to take an argument hash instead. This has the benefits of not requiring a specific order, letting consumers name the arguments, creating clarity on the consuming side, and having the consistency of a single argument for most/if not all functions that take input.
We can take advantage of these attributes and realize one of our own in our
prepForFight function. If we have multiple dependencies, we can put these dependencies and their defaults inside the argument hash, and no consumer has to know anything about it. There’s no ordering problem. There’s no null arugment passing. We just specify the keys that we care to specify and have defaults for the rest. A minor refactor might yield some destructuring of a single object sent to the function:
1 2 3 4 5 6 7
I think that’s probably our final refactor for now. What could we do to make this better? What are other stubbing methods that you’ve gotten a lot of mileage out of?