Learning a framework as you’re TDD-ing a project can be dangerous. But even if you
know what you’re doing, some of the conveniences of node.js can make you a bit sloppy
with your code. Namely, the ability to pull functions from files as you need them and pass
them around makes for some interesting problems.
If your functions are free of side effects, you’re not exactly committing
a serious coding sin. But go beyond the simple example above – starting to pass
around whole objects, or breaking the law of demeter – and soon your code doesn’t feel right.
I quickly ran into this problem with my vows tests. As I was about to merge a feature
branch to master, I reviewed my code and was a bit horrified at what I saw in my tests:
And this was just one of my tests. Yikes. Lots of bad things going on here:
My test has a dependency on my model implementation, and not just the model itself;
if I switch the model off mongoose in the future, my test itself will need to change.
My test has a dependency on a particulra spying/mocking framework (Sinon)
There’s a lot of confusing imports; why is server required off of vows?
Repeating requires - one for cleanupDB and one for Factory.
Bad bad bad.
My tests needed to be refactored to deal with this madness. I realized that there was
repeated code throughout my vows tests; not only could I clean things up with some proper
encapsulation, I could DRY my vows as well.
One cool feature of node is that if you include an index file in a directory, that
file is loaded when you require the directory. So I created a new spec/helpers directory,
and placed an index file inside.
My test logic dependencies are clean and encapsulated, making for both clearer
requires blocks and clearer tests themselves.
user_spec.coffee
12345678910111213141516171819202122232425262728
{Model,Test,Server}=require"../helpers"vows = require'vows'User = Model.getType"User"#code removed for brevity.addBatch"Hashes password:":"when a new user is created":topic: ->@user = Model.Factory.build"User"Test.spyUser,"makeSalt"Test.spyUser,"hashPassword"@user.save@callbackreturn"it creates a salt and stores it":(err,user) ->Test.assert.okUser.makeSalt.calledOnceTest.assert.okuser.salt==User.makeSalt.returnValues[0]"it encrpyts the password":(err,user) ->Test.assert.okUser.hashPassword.calledOnceTest.assert.okUser.hashPassword.calledWith@user.passwordteardown: ->User.hashPassword.restore()User.makeSalt.restore()Model.cleanupDB()