I know, tests are important. I know, the earlier I start writing automated tests the better. I know, a good regression test basis is invaluable. I know, tests are my security net, when I plan major refactorings. I know, tests enable progress. I know, tests give me another healthy perspective of the functionality I have to develop. I know, tests can help in proving that my application is actually doing what it is supposed to do. I know …
But I also know, how painful it can be to keep tests green, when changing the application code. I know, how difficult it is to write tests in some cases. I know, thoroughly tested applications tend to have much more test code than application code. I know, test code doesn’t provide any direct customer value. I know …
I know it is a trade off. I admit, I hesitate to write tests. Especially when trying new things out. While learning those new things, the application’s code will change dramatically. And I always do new things. Things I already know well, tend to bore me. I’m always striving to learn something new. Not everything new, but some major aspect.
My strategy is to skip tests until a hard nut appears. Then I look into the testing possibilities briefly (in case I engage a new base technology). If there is nothing simple and obvious, I try to crack the nut without the safety net. But then, usually not much later there appears a second hard nut. When this happens, I put some more energy in providing tests for both of the nuts I found so far. The goal: assuring that shell regenerating attempts are becoming visible, when trodding on.
Well, that was a lengthy intro.
The project proposal management app, I mentioned in earlier posts, just gave me the second hard nut to crack. Time for tests. The first nut was the handling of tree sets – basically moving arbitrary things around like in a file browser. I implemented that before, and it shouldn’t be that difficult. I had some problems, though, might be, that the chosen representation was a little unlucky, or that there were to many capabilities and constraints I pursued. Might be the combination. It works now, but it was a longer journey and it feels really vulnerable to changes.
Since unit testing in Meteor apps is far from being simple and obvious, I waited for the second hard nut. This one hit me while working on adding fulltext-search. There is no ready-to-use library I know of, so I decided to roll my own. This really exited me. But soon I realized, that I should be able to prove, that the different aspects of the search engine are working as expected. Manual testing this functionality through the user interface is no option, since there are many things happening under the hood. I wanted to test those in isolation. Well that calls for unit testing the different functions that are doing the work.
I mentioned it earlier: unit tests are no core part of Meteor, for now. There are plans to provide some official way of testing the app with version 1.0 of Meteor. Till then, you have to hack a bit. Well, what actually is the problem? There are numerous testing libraries available for Node.js, why can’t you simply use them? I asked myself, did some research and found out, that the basic tools are there. I settled on Mocha as the primary driver for tests. It seems to be everybody’s darling right know. I further decided to use Sinon.js, which seems to be the divine being, between the ordinary mocking/stubbing/spying frameworks out there.
So when I want to test the function: tokenize in the file: fulltext.coffee, how do I do that? Well Meteor does help a little. You can create a tests folder inside your apps root folder. This folder will be ignored by Meteor, so it’s a good place for your test code, without fearing that it might be mixed with your app’s code. There is even another place. Since search is a quite common feature I decided to make a Meteor package. The package folders are safe for test code as well.
In the “packages/fulltext” folder I decided to take the default route for mocha and created a test folder (notice the missing ‘s’) for my test code. On the root level lies the file that contains the function to be tested. So far so good. But how do I actually access the to-be-tested function from my test? I tried the obvious:
testling = require '../fulltext.coffee'
Ran the test from the package root with:
mocha --compilers coffee:coffee-script
And mocha answered with this error:
/home/dirk/Development/ProPro/ProPro/packages/fulltext/fulltext.coffee:96 FulltextIndex = new Meteor.Collection('fulltextindex'); ^ ReferenceError: Meteor is not defined at Object.<anonymous> (/home/dirk/Development/ProPro/ProPro/packages/fulltext/fulltext.coffee:96:25) at Object.<anonymous> (/home/dirk/Development/ProPro/ProPro/packages/fulltext/fulltext.coffee:107:4) at Module._compile (module.js:449:26) at Object.loadFile (/usr/local/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:179:19) at Module.load (module.js:356:32) ...
This didn’t bother me much at first, since I didn’t start Meteor, the missing global variable wasn’t really a surprise. The fun began as I tried to make a replacement (mock, stub, whatever) for Meteor and pass that to the to-be-tested file. Running Meteor and somehow executing the tests from the inside, wasn’t an obvious path either. So I tried another naive solution: defining a Meteor variable before requiring the to-be-tested file. But unfortunately require doesn’t pass the context down.
Well, the last resort, make some bridge code yourself. I called it testling and you can find it here and it’s also available from npm:
npm install unit-testling
You use it similar to require: pass the relative path to the to-be-tested file as the first parameter. You can optionally pass a second parameter: it expects an object and injects its attributes into the context of the file. The following gist shows an example.[gist https://gist.github.com/Crenshinibon/5597162]
I know, this post is already quite long. But there is one aspect I learned, as I developed testling, that is worth mentioning and will finish this post: resolving relative paths correctly. The problem: I wanted to be able to enter the path to the to-be-tested file, relative to the test file. There is no direct way to achieve this, the context only knows about the path to the executable and the seed script, which is not the test, but mocha. To resolve paths correctly one has to look at the call stack. I took this advice and parts of the code from an answers given here.