SHARE
Facebook X Pinterest WhatsApp

Spy on JavaScript Methods Using the Jasmine Testing Framework

Written By
thumbnail
Rob Gravelle
Rob Gravelle
Feb 7, 2013

In the Testing JavaScript Using the Jasmine Framework article, we learned how to test our JavaScript code using a JavaScript enabled browser and the Jasmine Testing Framework. In this article, we’re going to move on to spying on our methods using mocks.

Re-Mock-able

One of the primary aims of unit testing is to isolate a method or component that you want to test and see how it behaves under a variety of circumstances. These might include calls with various arguments – or even none at all, – or whether it calls other methods as it should. Unfortunately, many methods and/or objects have dependencies on other methods and/or objects, such as network connections, data sources, files, and even previously executed methods. This is where mocks come in. A mock is a fake object that poses as the real McCoy in order to satisfy the inherent dependency(ies) without having to go through the overhead of creating the real object.

Mocks work by implementing the proxy pattern. When you create a mock object, it creates a proxy object that takes the place of the real object. We can then define what methods are called and their returned values from within our test method. Mocks can then be utilized to retrieve run-time statistics on the spied function such as:

  1. How many times the spied function was called.
  2. What was the value that the function returned to the caller.
  3. How many parameters the function was called with.

In Jasmine, mocks are referred to as spies. There are two ways to create a spy in Jasmine: spyOn() can only be used when the method already exists on the object, whereas jasmine.createSpy() will return a brand new function:

//spyOn(object, methodName) where object.method() is a function
spyOn(obj, 'myMethod')

//jasmine.createSpy(stubName);
var myMockMethod = jasmine.createSpy('My Method');

As we’ll soon see, both of the above methods have their place in your unit tests.

Using the spyOn() Method

As mentioned above, spyOn() can only be used when the method already exists on the object. For simple tests, this is your best bet.

Our test cases all feature the following Person object. It has a couple of attributes, a getter and setter for the name, and two public methods:

var Person = function() { 
    //defaults
    var _age  =  0,
        _name = 'John Doe';

    this.initialize = function(name, age) {
      _name = name || _name;
      _age  = age  || _age;
    };
    if (arguments.length) this.initialize();
     
    //getters and setters
    this.getName     = function()      { return _name; };
    this.setName     = function (name) { _name = name; };

    //public methods
    this.addBirthday = function()      { _age++; };
    this.toString    = function()      { return 'My name is " + this.getName() + " and I am " + _age + " years old.'; };
}; 

Say that we want to verify that the toString() method was calling getName(). We would instantiate the Person as usual, but before calling toString(), we would call spyOn(), passing in the person instance and the name of the method that we want to spy on (‘getName’). We can then call jasmine matchers to see what happened. The simplest test is to check that getName() was in fact called:

describe("Person toString() Test", function() {
    it("calls the getName() function", function() {
        var testPerson = new Person();
        spyOn(testPerson, "getName");
        testPerson.toString();
        expect(testPerson.getName).toHaveBeenCalled();
    });
});

But that’s just the beginning. We can run other tests on our spied function, such as what arguments it was called with. The toHaveBeenCalledWith() method accepts a value to be compared against the method’s arguments attribute. Conversely, we can test that the function was called without any parameters by calling toHaveBeenCalledWith() without a value:

describe("Person toString() Test", function() {
    var testPerson;
    beforeEach(function() { testPerson = new Person(); });   
    afterEach (function() { testPerson = undefined;    });
    
    it("calls the getName() function", function() {
        spyOn(testPerson, "getName");
        testPerson.toString();
        expect(testPerson.getName).toHaveBeenCalled();
    });
    
    it("Method getName() was called with zero arguments", function() {
        // Ensure the spy was called with the correct number of arguments
        // In this case, no arguments
        expect(testPerson.getName).toHaveBeenCalledWith();
        // this also works
        // expect(testPerson.getName.mostRecentCall.args.length).toEqual(0);
    });
});

 

Creating Our Own Spy Method

Sometimes, it may be beneficial to completely replace the original method with a fake one for testing. Perhaps the original method takes a long time to execute, or it depends on other objects that aren’t available in the test context. Jasmine lets us handle this issue by creating a fake method using jasmine.createSpy(). Here’s how to substitute a fake getName() for the real one:

describe("Person toString() Test with Fake getName() Method", function() {
    it("calls the fake getName() function", function() {
        var testPerson = new Person();
        testPerson.getName = jasmine.createSpy("getName spy");
        testPerson.toString();
        expect(testPerson.getName).toHaveBeenCalled();
    });
});

Unlike spyOn(), creating a fake method circumvents the original method so that it is not called during tests. Thus, the alert in getName() below will not appear:

var Person = function() { 
    //...
    this.getName = function() { 
      alert("You called?");  //won't be called
      return _name; 
    };
    //...
}; 

describe("Person toString() Test with Fake getName() Method", function() {
    it("calls the fake getName() function", function() {
        var testPerson = new Person();
        testPerson.getName = jasmine.createSpy("getName() spy");
        testPerson.toString();
        expect(testPerson.getName).toHaveBeenCalled();
    });
});

Modifying the Fake Method

If your method is being called by another method, you may want it to return something. You can tell Jasmine what to return using the andReturn(value) method:

testPerson.getName = jasmine.createSpy("getName() spy").andReturn("Bobby");

And finally, here’s a way to substitute an entirely different method body for the original:

// defining a spy on an existing property: testPerson.getName() calls an anonymous function
testPerson.getName = jasmine.createSpy("getName() spy").andCallFake(function() {
    console.log("Hello from getName()");
    return "Bobby";
});

The above function not only returns “Bobby” each time, but it also logs a message to the console. That would be a little harder to do with the original function.

Conclusion

Today’s article only scratched the surface of what can be done using Jasmine spies. In the next part, we’ll examine using Jasmine to test asynchronous methods.

Recommended for you...

The Revolutionary ES6 Rest and Spread Operators
Rob Gravelle
Aug 23, 2022
Ahead of Time (AOT) Compilation in Angular
Tariq Siddiqui
Aug 16, 2022
Converting a JavaScript Object to a String
Rob Gravelle
Aug 14, 2022
Understanding Primitive Type Coercion in JavaScript
Rob Gravelle
Jul 28, 2022
HTML Goodies Logo

The original home of HTML tutorials. HTMLGoodies is a website dedicated to publishing tutorials that cover every aspect of being a web developer. We cover programming and web development tutorials on languages and technologies such as HTML, JavaScript, and CSS. In addition, our articles cover web frameworks like Angular and React.JS, as well as popular Content Management Systems (CMS) that include WordPress, Drupal, and Joomla. Website development platforms like Shopify, Squarespace, and Wix are also featured. Topics related to solid web design and Internet Marketing also find a home on HTMLGoodies, as we discuss UX/UI Design, Search Engine Optimization (SEO), and web dev best practices.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.