Devotees of unit-testing in languages like C# know the importance of interfaces and
mocking. When using
Jasmine to unit-test JavaScript, these concepts are even more central. Why more central? Because Jasmine's mocking capability is even more powerful than, for example, the excellent
Moq library for C#. You can easily replace any function with behavior of your own design -- even functions in the system objects.
We can see an example in the Simulated World I introduced in the
last post. The simulated beings of that world behave according to randomly generated numbers. To test their behavior in a controlled way, we can replace the normal sequence of numbers that
Math.Random() generates with our own.
This function, at the top of tests.js in
the Plunker from the previous post, will call
func
once for each "random" number we provide in
arrayOfRandoms
. Before each call, we set
ixRandom
so the random number we want is returned from the array. You'll see
Jasmine calls its mocking mechanism spies.
var doWithRandoms = function(arrayOfRandoms, func) {
var ixRandom = 0;
var spy = spyOn(Math,'random').andCallFake(function() {
return arrayOfRandoms[ixRandom];
});
for (ixRandom=0; ixRandom<arrayofrandoms.length; ++ixrandom) {
func();
}
removespy(spy);
};
Note the call to spyOn
in the third line. This tells Jasmine to watch for calls to a function and do something special. The syntax is spyOn(object, functionName)...
. After the spyOn
, you have several choices in a fluent interface, including
andReturn(someValue)
to immediately return a value;andCallFake(function(parameters) { ... })
to replace the normal function with your own; andandCallThrough()
to call the original function.
That's in Jasmine 1.3.1. The syntax in Jasmine 2.0 is slightly different.
You can use method-chaining to do things like
spyOn(myObject, 'myFunction').andCallFake(...).andCallThrough()
. That will insert custom behavior (the fake) before calling the normal function.
In unit testing, we always want to protect the test from the behavior of whatever we're
not testing. Jasmine's spies are so easy to use that if I'm testing one function in an object, I find myself replacing the object's other member functions with spies. Here's an example from tests.js in the same
the Plunker.
describe('encounter(sim1, sim2)', function() {
it('changes both healths per the getHealthChanges function', function() {
var a = sim.create(0,0);
var b = sim.create(0,0);
var initialHealth = a.getHealth();
spyOn(sim,'getHealthChanges').andReturn([1000, -1000]);
sim.encounter(a,b);
expect(a.getHealth()).toBe(initialHealth + 1000);
expect(b.getHealth()).toBe(initialHealth - 1000);
});
We are testing the
sim.encounter
function, which relies on values from
sim.getHealthChanges
. Rather than expose the test of
sim.ncounter
to possible problems in
sim.getHealthChanges
, we force the latter function to return predictable results.