How to Use Spies in Jasmine

by Larry Spencer Monday, June 30, 2014 3:06 AM
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; and
  • andCallThrough() 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.

Tags: , ,

All | Talks

About the Author

Larry Spencer

Larry Spencer develops software with the Microsoft .NET Framework for ScerIS, a document-management company in Sudbury, MA.