6
\$\begingroup\$

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 = '<span data-value-off="AM" data-value-on="PM"></span>"';
 $("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 = '<span></span>';
 $("body")[0].appendChild(element);
 options = {
 element: $(element).find("span")[0],
 onToggle: function(status) {}
 };
 myToggle = new MySwitch(options);
 $(element).removeAttr("data-value-on").removeAttr("data-value-off");
 expect($(element).text()).toMatch("OFF");
 myToggle.toggle();
 expect($(element).text()).toMatch("ON");
});
it("Should have AM as off value", function() {
 expect($(element).text()).toMatch("AM");
});
it("Should have PM as on value", function() {
 expect($(element).text()).toMatch("AM");
});
it("Should toggle AM->PM->AM", function() {
 myToggle.toggle();
 expect($(element).text()).toMatch("PM");
 myToggle.toggle();
 expect($(element).text()).toMatch("AM");
});
it("Should call onToggle callback", function() {
 spyOn(options, "onToggle");
 myToggle.toggle();
 expect(options.onToggle).toHaveBeenCalled();
 });
});
Quill
12k5 gold badges41 silver badges93 bronze badges
asked Nov 13, 2014 at 0:34
\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

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);
 });
 });
});
answered Apr 16, 2015 at 8:13
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.