HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Jasmine unit tests for simple toggle class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simplejasmineteststoggleforclassunit

Problem

Using JavaScript/jQuery I created a simple toggle and started writing unit tests for it using Jasmine. I am not sure, however, if I should be testing for things like e.g. if "click" listener is registered when object is instantiated. Should this be done as part of unit test or rather E2E test using WebDriver or something? At the moment, I'm mostly checking if e.g. toggle function changes content of HTML as expected but am a bit lost in how far I should take my unit testing.

MySwitch = function(options) {

    this.options = options;
    this._$el = $(options.element);
    this._status = 0;
    this._$el.data("value-on") ? this._valueOn = this._$el.data("value-on") : this._valueOn = "ON";
    this._$el.data("value-off") ? this._valueOff = this._$el.data("value-off") : this._valueOff = "OFF";
    if (!this._$el.text()) this._$el.text(this._valueOff);

    this._$el.on("click", $.proxy(this.toggle, this));
};

MySwitch.prototype.toggle = function(el) {

    this._status ? (this._$el.text(this._valueOff), this._status = 0) : (this._status = 1, this._$el.text(this._valueOn));

    if (typeof this.options.onToggle === "function")
        this.options.onToggle.call(this, this._status);
};

MySwitch.prototype.getStatus = function(el) {

    return this._status;
};


And tests:

```
describe("My switch", function() {

var options, element, myToggle;

beforeEach(function() {

element = document.createElement("div");

element.innerHTML = '"';

$("body")[0].appendChild(element);

function onToggleCallback() {};

options = {

element: $(element).find("span")[0],
onToggle: onToggleCallback
};

myToggle = new MySwitch(options);
});

afterEach(function() {

$("body > div")[0].remove();

});

it("Should set ON/OFF as default values", function() {

$("body > div")[0].remove();

element = document.createElement("div");

element.innerHTML = '';

$("body")[0].appendChild(element);

options = {

Solution

To begin with, don't interact with the DOM. The reason for that is imagine that you have hundreds of unit tests. Do you want to clean up the DOM after them? Are you going to forget sometimes? If you forget will you be able to find why unit tests are failing? When you will be setting up a Continuous Integration process which will run the unit tests will you use a JavaScript engine tool that is aware of a DOM? What I'm getting at is only test the logic.

In order to make your class unit testable it needs to be refactored. To begin with, you need to make the constructor not do anything except set the this.options and this._status and move all the other code to an init method:

MySwitch = function(options) {
    this.options = options;
    this._status = 0;
};

MySwitch.prototype.init = function() {
    this._$el = $(options.element);
    this._$el.data("value-on") ? this._valueOn = this._$el.data("value-on") : this._valueOn = "ON";
    this._$el.data("value-off") ? this._valueOff = this._$el.data("value-off") : this._valueOff = "OFF";
    if (!this._$el.text()) this._$el.text(this._valueOff);

    this._$el.on("click", $.proxy(this.toggle, this));  
};


Now refactor the toggle method:

MySwitch.prototype.toggle = function() {
    this.toggleStatus();
    this.setText();
    this.runToggleFunction();
};

MySwitch.prototype.runToggleFunction = function() {
    if (typeof this.options.onToggle === "function")
        this.options.onToggle.call(this, this._status);
};

MySwitch.prototype.toggleStatus = function() {
    this._status = this._status === 0 ? 1 : 0;
};

MySwitch.prototype.setText = function() {
    this.getEl.text( this._status ? this._valueOn : this._valueOff );
};

MySwitch.prototype.getEl = function() {
    return this._$el;
};


Now you can test the code:

describe("MySwitch", function() {

    it("toggle()", function() {
        var mySwitch = new MySwitch();

        spyOn(mySwitch, 'toggleStatus');
        spyOn(mySwitch, 'setText');
        spyOn(mySwitch, 'runToggleFunction');

        mySwith.toggle();

        expect(mySwitch.toggleStatus).toHaveBeenCalled();
        expect(mySwitch.setText).toHaveBeenCalled();
        expect(mySwitch.runToggleFunction).toHaveBeenCalled();
    });

    describe('runToggleFunction()', function() {
        it('callback provided', function() {
            var func = jasmine.createSpy('func');
            var mySwitch = new MySwitch({onToggle: func});
            var status = 1;

            mySwitch._status = status;
            mySwitch.runToggleFunction();

            expect(func).toHaveBeenCalledWith(status);
        });

        it('no callback', function() {
            var mySwitch = new MySwitch();

            try {
                mySwitch.runToggleFunction();
                expect(true).toBe(true);
            } catch (e) {
                expect(true).toBe(false);
            }
        });
    });

    it('toggleStatus()', function() {
        var mySwitch = new MySwitch();

        mySwitch._status = 1;
        mySwitch.toggleStatus();

        expect(mySwitch._status).toBe(0);
    });

    describe('setText()', function() {

        var valueOn = "on";
        var valueOff = "off";
        var mySwitch;
        var $el;

        beforeEach(function() {
            mySwitch = new MySwitch();

            $el = {
                text: function(){}
            };

            spyOn($el, 'text');
            spyOn(mySwitch, 'getEl').and.returnValue($el);
        });

        it('status 0', function() {
            mySwitch._status = 0;
            mySwitch.setText();
            expect($el.text).toBe(valueOff);
        });

        it('status 1', function() {
            mySwitch._status = 1;
            mySwitch.setText();
            expect($el.text).toBe(valueOn);
        });
    });

});

Code Snippets

MySwitch = function(options) {
    this.options = options;
    this._status = 0;
};

MySwitch.prototype.init = function() {
    this._$el = $(options.element);
    this._$el.data("value-on") ? this._valueOn = this._$el.data("value-on") : this._valueOn = "ON";
    this._$el.data("value-off") ? this._valueOff = this._$el.data("value-off") : this._valueOff = "OFF";
    if (!this._$el.text()) this._$el.text(this._valueOff);

    this._$el.on("click", $.proxy(this.toggle, this));  
};
MySwitch.prototype.toggle = function() {
    this.toggleStatus();
    this.setText();
    this.runToggleFunction();
};

MySwitch.prototype.runToggleFunction = function() {
    if (typeof this.options.onToggle === "function")
        this.options.onToggle.call(this, this._status);
};

MySwitch.prototype.toggleStatus = function() {
    this._status = this._status === 0 ? 1 : 0;
};

MySwitch.prototype.setText = function() {
    this.getEl.text( this._status ? this._valueOn : this._valueOff );
};

MySwitch.prototype.getEl = function() {
    return this._$el;
};
describe("MySwitch", function() {

    it("toggle()", function() {
        var mySwitch = new MySwitch();

        spyOn(mySwitch, 'toggleStatus');
        spyOn(mySwitch, 'setText');
        spyOn(mySwitch, 'runToggleFunction');

        mySwith.toggle();

        expect(mySwitch.toggleStatus).toHaveBeenCalled();
        expect(mySwitch.setText).toHaveBeenCalled();
        expect(mySwitch.runToggleFunction).toHaveBeenCalled();
    });

    describe('runToggleFunction()', function() {
        it('callback provided', function() {
            var func = jasmine.createSpy('func');
            var mySwitch = new MySwitch({onToggle: func});
            var status = 1;

            mySwitch._status = status;
            mySwitch.runToggleFunction();

            expect(func).toHaveBeenCalledWith(status);
        });

        it('no callback', function() {
            var mySwitch = new MySwitch();

            try {
                mySwitch.runToggleFunction();
                expect(true).toBe(true);
            } catch (e) {
                expect(true).toBe(false);
            }
        });
    });

    it('toggleStatus()', function() {
        var mySwitch = new MySwitch();

        mySwitch._status = 1;
        mySwitch.toggleStatus();

        expect(mySwitch._status).toBe(0);
    });

    describe('setText()', function() {

        var valueOn = "on";
        var valueOff = "off";
        var mySwitch;
        var $el;

        beforeEach(function() {
            mySwitch = new MySwitch();

            $el = {
                text: function(){}
            };

            spyOn($el, 'text');
            spyOn(mySwitch, 'getEl').and.returnValue($el);
        });

        it('status 0', function() {
            mySwitch._status = 0;
            mySwitch.setText();
            expect($el.text).toBe(valueOff);
        });

        it('status 1', function() {
            mySwitch._status = 1;
            mySwitch.setText();
            expect($el.text).toBe(valueOn);
        });
    });

});

Context

StackExchange Code Review Q#69674, answer score: 2

Revisions (0)

No revisions yet.