Skip to content

Instantly share code, notes, and snippets.

@dylanjha
Last active January 1, 2016 15:49
Show Gist options
  • Select an option

  • Save dylanjha/8166529 to your computer and use it in GitHub Desktop.

Select an option

Save dylanjha/8166529 to your computer and use it in GitHub Desktop.
Am I doing this wrong? My goal here is to set up a constructor that I can test. The first bit is how I would normally create this new constructor MyObject... the second bit shows how I have to change it in order to test some of the internal functions. See my first comment below.
//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);
@dylanjha
Copy link
Author

The main point here is that it feels a bit strange that I have to expose some of the private methods of MyObject just because I want to test them.

P.S. I'm using jasmine, but I don't think that matters much, the idea should be the same.

@jsatk
Copy link

jsatk commented Dec 29, 2013

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.

@jsatk
Copy link

jsatk commented Dec 29, 2013

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!

  1. 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.
  2. 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.

@dylanjha
Copy link
Author

dylanjha commented Jan 2, 2014

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment