I've written some Meteor methods for my application containing various chunks of business logic. Now I'd like to write unit tests for those methods. By unit tests, I specifically mean fast tests which do not:
How can I go about doing this? My current thinking is that when I start up the Meteor server in a test configuration I should replace my collections with dummy collections (by passing new Meteor.Collection(null)
) and have my unit tests run on the server side, invoking Meteor.call() on each of my methods in turn from there. I'm not entirely sure how I'd kick off the tests, possibly I'd want to build a custom /tests
URL into my application which fires them off. Does this approach seem reasonable? Are there any libraries/packages out there that would make writing unit tests for my methods easier?
To run all the tests in a default group, choose the Run icon and then choose the group on the menu. Select the individual tests that you want to run, open the right-click menu for a selected test and then choose Run Selected Tests (or press Ctrl + R, T).
The assert section ensures that the code behaves as expected. Assertions replace us humans in checking that the software does what it should. They express requirements that the unit under test is expected to meet. Now, often one can write slightly different assertions to capture a given requirement.
A unit test is a function you write that tests something about your app. A good unit test is small. It tests just one thing in isolation. For example, if your app adds up the total amount of time your user spent doing something, you might write a test to check if this total is correct.
All right, here's what I came up with to unit test my methods. I'll be the first to admit there's a lot of room for improvement in this!
First, in my server.coffee
file I have the following code:
Meteor.startup ->
return unless Meteor.settings["test"]
require = __meteor_bootstrap__.require
require("coffee-script")
fs = require("fs")
path = require("path")
Mocha = require("mocha")
mocha = new Mocha()
files = fs.readdirSync("tests")
basePath = fs.realpathSync("tests")
for file in files
continue unless file.match(/\.coffee$/) or file.match(/\.js$/)
continue if file[0] == "."
filePath = path.join(basePath, file)
continue unless fs.statSync(filePath).isFile()
mocha.addFile(filePath)
mocha.run()
First of all this code only runs if Meteor.settings["test"] has been defined, which I can do when I run my tests locally, but which should never be true in production. It then searches the "tests" directory for javascript or coffeescript files (subdirectories aren't searched in my implementation, but it would be easy to add that) and adds them to a mocha
instance. I'm using the excellent mocha javascript testing library here, combined with the chai assertion library.
All of this code is wrapped inside a Meteor.startup
call so that my unit tests run on server start. This is especially nice because Meteor automatically re-runs my tests whenever I change any of my code. Because of the decision to isolate out the database and not perform XHRs, my tests run in a few milliseconds, so this isn't very annoying.
For the tests themselves, I need to do
chai = require("chai")
should = chai.should()
To pull in the assertion library. There are still a couple tricky problems to be solved, though. First of all, Meteor method calls will fail if they're not wrapped in a Fiber. I don't currently have a very good solution to this problem, but I created the itShould
function to replace mocha's it
function and wrap the test body inside a Fiber:
# A version of mocha's "it" function which wraps the test body in a Fiber.
itShould = (desc, fn) ->
it(("should " + desc), (done) -> (Fiber ->
fn()
done()).run())
Next up is the problem of, for testing purposes, replacing my collections with mock collections. This is very difficult to do if you follow the standard Meteor practice of putting your collections in global variables. However, if you make your collections properties on a global object, you can do it. Simply make your collections via myApp.Collection = new Meteor.Collection("name")
. Then, in your tests, you can have a before
function mock out the collection:
realCollection = null
before ->
realCollection = myApp.Collection
myApp.Collection = new Meteor.Collection(null)
after ->
myApp.Collection = realCollection
This way, your collection is mocked out for the duration of the test run, but then it's restored so you can interact with your app normally. Some other things are possible to mock via a similar technique. For example, the global Meteor.userId()
function only works for client-initiated requests. I've actually filed a bug against Meteor to see if they can provide a better solution to this problem, but for now I'm just replacing the function with my own version for testing:
realUserIdFn = null
before ->
realUserIdFn = Meteor.userId
Meteor.userId = -> "123456"
after ->
Meteor.userId = realUserIdFn
This approach works for some parts of Meteor, but not all of it. For example, I haven't found a way to test methods that invoke this.setUserId
yet, because I don't think there's a good way to mock out that behavior. On the whole, though, this approach is working out for me... I love being able to re-run my tests automatically when I change the code, and running tests in isolation is just generally a good idea. It's also very convenient that tests on the server can block, making them simpler to write without callback chains. Here's what a test would look like:
describe "the newWidget method", ->
itShould "make a new widget in the Widgets collection", ->
widgetId = Meteor.call("newWidget", {awesome: true})
widget = myApp.Widgets.findOne(widgetId)
widget.awesome.should.be.true
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With