Most of the time, spies are used to mock dependencies on existing methods of existing objects. Most people pass in the real object that holds the real function, and spy on that function to make sure it gets called at the right time. This works. I do this a lot and you can see it in the following spec on line 9
But Now I find myself not wanting to bring in the entire dependency object in to the life of my spec. I don’t want to have to reach out to other places, other modules in my system, just to bring that one object in to the test. It’s this one lines at the top of my spec that bothers me:
In this one line, I’m reaching out to another module in my system and requiring it in to my spec. I’m breaking down the barriers of separation between these two modules by coupling the specs of one module to the implementation found in another.
Yeah, sure that’s ok sometimes – when I have a foundation or plumbing module installed through NPM. I don’t mind requiring underscore.js into my spec, for example. But my business system modules have a very clear separation that should not be torn down. The way they coordinate is not through direct communication and inter-dependence, but through a mediator that coordinates between them. My specs, then, should not be breaking down a barrier that exists for good reason in my production code.
Enter Mock Objects
So I built my own mock object to work in place of the actual object, using simple constructor functions and jasmine spies. The basics of it look like this:
In this simple mock, I am dynamically building the API (“protocol”) of the original when the object is instantiated. This lets each instance have it’s own spies and stubs – a necessary thing when using the mock in more than one spec.
The mock is doing more than just adding spies, though. I’m inheriting the EventEmitter on to the mock object so that I can have real emit and on methods to use. I’m still spying on those methods, but I’m allowing those to be called through to the original. This lets me spy on the methods as needed, but still performs the needed event triggering and handling that my code relies on.
With this in place, I can go back to my original spec and change things up a bit. Instead of requiring the original module in to the spec, I can just require the mock object and use that as the dependency.
Using the mock, I was also able to get rid of the spyOn call in the spec itself, because the method was already a spy – bonus!
And now that my specs are using mock objects that meet the same protocol requirements as the real object, I don’t have to worry so much about the barriers between my modules being torn apart. My Schedule module is no longer dependent on my Job module. This is a better reflection of the production code where a mediator object facilitates communication between the two objects, and allows the code to remain decoupled in a clean manner.
Built-In Spy Objects With Jasmine
If you don’t need custom code like my emitters that are spied upon, you can use the built in createSpyObject in Jasmine, to reduce the code a bit.
Thanks to Dave Mosher for pointing this out via twitter:
— Dave Mosher (@dmosher) April 23, 2014
It’s Not All Rainbows And Unicorns
For as much as I like this approach, at the moment, I can already see some downsides and potential problems. Sometimes unicorns explode in to nasty bits and guts, or start breathing fire after rapidly morphing in to a Balrog.
The single largest problem is the notion of a “protocol” in a dynamic language. Since this is an API definition that is not codified but rather documented in to existence, it becomes a place where danger lurks. If you are diligent in changing the protocol in the mock object and in the specs for the object that uses the mock, your tests will still pass. But if you lapse in changing the protocol implementation on the real object, you will quickly find that your production code fails.
A Need For More Than Unit Tests
This is a situation where you’ll find unit testing fails you – a situation that arises faster in dynamic languages than static languages, in my experience. The solution here, is functional or acceptance tests – something that runs from a higher perspective in the app. Maybe it’s selenium or webdriver or whatever the latest flavor of driving a real browser through the web app is. Maybe it’s still just Jasmine, but written to fully use the entire application stack (database and all). Whatever the case is, having a good balance between “unit” tests and functional or application level tests will help save your hide, here. When a unit test passes because the protocol has been updated on the mock, a functional or application level test will cover your by failing because the production code for the real object that implements the protocol has not been updated.
So Much More Exploring To Do
I think mock objects like this are going to become important in the nodejs testing realm, as people start seeing the need to keep application / system modules cleanly separated from each other. But mock objects certainly are not a panacea of solution. Rather, they present additional possibilities for failure if you’re not careful. Used wisely and in combination with higher level tests that exercise more of the system at once, though, I think mocks can add a lot of value to a test suite.
At least, that’s what I think right now. I’m still exploring this space, and finding the dark corners and hidden monsters. I hope to stop before my gold lust unearth’s a Balrog too powerful for me to fight. But knowing me, I’ll find it, poke it a few time with a sharp stick and don my wizard hat and staff as we plummet through the Earth in a battle to the death.
P.S. If you’re not familiar with Jasmine and testing NodeJS, check out what I’ve got coming up in my WatchMeCode screencasts. I have a small series on introducing Jasmine to test NodeJS in the pipeline – already recorded and edited. Subscribe now to get yourself caught up with past episodes, and stick around for the Jasmine testing series. It will shed some light on the content above.