Last Updated: March 02, 2016
·
2.339K
· xander

Testing if object is an instance of a class/function when constructor is not available in the scope

Before I did any serious unit testing in JS I did a lot of it in PHP. When I wanted to verify if something is an instance of something else I simply did something like:

$this->assertTrue($foo instanceof Bar);

It is possible in JS as well. However you need access to constructor to do so.

function foo(x) {
  function Foo(x) {
    // something
  }

  return new Foo(x);
}

y = foo(1);
// can't do "y instanceof Foo" because Foo is not in the current scope

Now imagine that you have this repository service in your angular application and you want to verify if 'getCurrent' responds with a promise:

(function (angular) {
    var app = angular.module('app');

    var UserRepository = function ($http, CONFIG) {
        var baseUrl = CONFIG.api.baseUrl;

        this.getCurrent = function () {
            return $http.get(baseUrl + '/me');
        };

        // more code here
    };

    app.service('UserRepository', ['$http', 'CONFIG', UserRepository]);
}(window.angular));

Prototypes to the rescue:

// we have $q service already injected
output = UserRepository.getCurrent();           expect(output.__proto__).toBe($q.defer().promise.__proto__);

Post Scriptum

Remember that JS is dynamically typed language and even if your object is based on some prototype doesn't mean that methods required by you wouldn't get unbound on the way. Most developers won't do stuff like that yet it's still possible. Let's get a little paranoid for a while. So...

Another way to test your object would be to verify if it has methods you need:

expect(typeof output.then).toBe('function');
expect(typeof output.success).toBe('function');
expect(typeof output.error).toBe('function');

It's so-called duck-typing - if it quacks and flies - it's a duck. If it has the the same set of method as a promise object we need - that means it is our promise object.

Yet it still doesn't mean that those functions will match the interface you need (input arguments and return value can be different from what you require).

If you want to be 100% sure that our 'getCurrent' returns exactly the same thing as $http.get - you can e.g. mock $http service for that unit test. One way to do so would be:

describe('UserRepository.getCurrent', function () {
    var httpMock;
    var UserRepository;
    var getReturnValue = {}; // just a plain object to be used to verify if both $http.get and UserRepository.getCurrent will return that exact object

    beforeEach(function () {
        module('app', function ($provide) {

            // create a mock
            httpMock = jasmine.createSpyObj('$http', ['get']);
            httpMock.get.and.callFake(function () {
                return getReturnValue;
            });

            // replace $http definition with our mock
            $provide.service('$http', function () {
                return httpMock;
            });
        });

        // inject service we're about to test - it will get our mocked $http
        inject(function (_UserRepository_) {
            UserRepository = _UserRepository_;
        });
    });

    // test
    it('should  return $http\'s promise', function () {
      expect(UserRepository.getCurrent()).toBe(getReturnValue);
    });
});

And that's it. You know a better way? I'm all yours.