-
-
Save dylanjha/8166529 to your computer and use it in GitHub Desktop.
| //this constructor, MyObject is supposed to only have 1 publically exposed method: .getResults() | |
| ;(function ( $, window, document, undefined ) { | |
| window.MyObject = function(params){ | |
| var error = params.error; | |
| function _shouldFireRequest = function(){ | |
| //some logic | |
| return boolean | |
| } | |
| function _requestResults = function(){ | |
| //makes a request | |
| } | |
| this.getResults = function(){ | |
| if(_shouldFireRequest()){ | |
| _requestResults(); | |
| } else { | |
| error() | |
| } | |
| } | |
| } | |
| })(jQuery, window, document); | |
| // if I want to test the _shouldFireRequest method, I need to expose it | |
| // as a method on the object as shown below. | |
| // My strategy is that once I expose it, I can create a new instance of MyObject: | |
| // myObject = new MyObject | |
| // set up some state on that instance, then call myObject._shouldFireRequest() | |
| // and test an expectation on the return value. | |
| ;(function ( $, window, document, undefined ) { | |
| // | |
| //same | |
| // | |
| this._shouldFireRequest = function(){ | |
| //some logic | |
| return boolean | |
| } | |
| this.getResults = function(){ | |
| if(this._shouldFireRequest()){ | |
| _requestResults(); | |
| } else { | |
| error() | |
| } | |
| } | |
| /// | |
| })(jQuery, window, document); | |
Some random nit-picky notes.
Judging from the style of your code I'm going to assume you're coming from a more "traditional" OOP language such as Java or C++. I'm going to nitpick because a) I like to b) I think it will make you a better JS coder!
- Don't use
_to start a method or function name. As you probably know JS doesn't really have classes or private methods. Because of that people have developed the hack of throwing a_in front of a method name to communicate that it's supposed to be private. There's really no such thing in JS (outside of prototype, but that's not really the same thing) and should most likely be avoided. - I love that you're using a self-invoking anonymous function and that you start it with a
;to ensure when you're code is minified it absolutely works. Great job.
Here's how I'd recommend structuring what you're going for:
;(function (window, undefined) {
'use strict';
var MyObject = {
shouldFireRequest: function () {
// some logic
return boolean;
},
requestResults: function () {
// makes a request
},
errorHandler: function () {
// Do some error stuff here
},
getResults: function (params) {
if (params.error) {
this.errorHandler();
return;
}
this.requestResults();
}
};
window.MyObject = MyObject || {};
}(this));
Your Jasmine tests for this could look something like this:
describe('MyObject', function () {
beforeEach(function () {
spyOn(MyObject, 'errorHandler');
});
it('calls errorHandler if there are errors in the params', function () {
var someParams = {
error: true
};
MyObject.getResults(someParams);
expect(MyObject.errorHandler).toHaveBeenCalled();
});
it('calls requestResults when error is falsy in the params', function () {
var someParams = {};
spyOn(MyObject, 'requestResults');
MyObject.getResults(someParams);
expect(MyObject.errorHandler).not.toHaveBeenCalled();
expect(MyObject.requestResults).toHaveBeenCalled();
});
});
If you need to create multiple instances of MyObject then try using prototype. This next method will allow you to use the new constructor but ensure that you only define certain methods that are reused and you do not want changed stay the same.
;(function (window, undefined) {
'use strict';
var AnotherObject = function () {
this.getResults = function (params) {
if (params.error) {
this.errorHandler();
return;
}
this.requestResults();
};
};
AnotherObject.prototype.shouldFireRequest = function () {
// some logic
return boolean;
};
AnotherObject.prototype.requestResults = function () {
// makes a request
};
AnotherObject.prototype.errorHandler = function () {
// Do some error stuff here
};
window.AnotherObject = AnotherObject || {};
}(this));
Your Jasmine tests for this could look something like this:
describe('AnotherObject', function () {
var obj;
beforeEach(function () {
obj = new AnotherObject();
spyOn(obj, 'errorHandler');
});
it('calls errorHandler if there are errors in the params', function () {
var someParams = {
error: true
};
obj.getResults(someParams);
expect(obj.errorHandler).toHaveBeenCalled();
});
it('calls requestResults when error is falsy in the params', function () {
var someParams = {};
spyOn(obj, 'requestResults');
obj.getResults(someParams);
expect(obj.errorHandler).not.toHaveBeenCalled();
expect(obj.requestResults).toHaveBeenCalled();
});
});
I hope you find this helpful and I hope I've somewhat answered your question / provided clarity.
Thanks so much. This is really helpful. In my case. I DO want to have multiple instances of this MyObject... that means, I will use the prototype strategy.
The main takeaway here, that perfectly answers my question is that instead of this._method, like I was doing, I should put those methods on the prototype.
That way, they're kinda from the main constructor, but more importantly they're exposed be able to test in isolation.
Hey, Dylan!
The very short answer is that you cannot test local methods using Jasmine. However, I think this speaks more to how test is structured. Most of the time with Jasmine you want to test the result of the test (typically the return value) or spyOn another method that it is supposed to call and ensure it gets called.
I'll follow up with some options/alternatives that I think will suite your needs and help you structure your JS to be more easily testable.