19

We have few methods in Angular Controller, which are not on the scope variable.

Does anyone know, how we can execute or call those methods inside Jasmine tests?

Here is the main code.

var testController = TestModule.controller('testController', function($scope, testService)
{

function handleSuccessOfAPI(data) {
    if (angular.isObject(data))
    {
       $scope.testData = data;
    }
}

function handleFailureOfAPI(status) {
    console.log("handleFailureOfAPIexecuted :: status :: "+status);
}

 // this is controller initialize function.
 function init() {
    $scope.testData = null; 

    // partial URL
    $scope.strPartialTestURL = "partials/testView.html;

    // send test http request 
    testService.getTestDataFromServer('testURI', handleSuccessOfAPI, handleFailureOfAPI);
}

 init();
}

Now in my jasmine test, we are passing "handleSuccessOfAPI" and "handleFailureOfAPI" method, but these are undefined.

Here is jasmine test code.

describe('Unit Test :: Test Controller', function() {
var scope;
var testController;

var httpBackend;
var testService;


beforeEach( function() {
    module('test-angular-angular');

    inject(function($httpBackend, _testService_, $controller, $rootScope) {

        httpBackend = $httpBackend;
        testService= _testService_;

        scope = $rootScope.$new();
        testController= $controller('testController', { $scope: scope, testService: testService});
            });
});

afterEach(function() {
       httpBackend.verifyNoOutstandingExpectation();
       httpBackend.verifyNoOutstandingRequest();
    });

it('Test controller data', function (){ 
    var URL = 'test server url';

    // set up some data for the http call to return and test later.
    var returnData = { excited: true };

    // create expectation
    httpBackend.expectGET(URL ).respond(200, returnData);

    // make the call.
    testService.getTestDataFromServer(URL , handleSuccessOfAPI, handleFailureOfAPI);

    $scope.$apply(function() {
        $scope.runTest();
    });

    // flush the backend to "execute" the request to do the expectedGET assertion.
    httpBackend.flush();

    // check the result. 
    // (after Angular 1.2.5: be sure to use `toEqual` and not `toBe`
    // as the object will be a copy and not the same instance.)
    expect(scope.testData ).not.toBe(null);
});
});
larrydahooster
  • 4,114
  • 4
  • 40
  • 47
furqan kamani
  • 721
  • 2
  • 7
  • 16

2 Answers2

10

I know this is an old case but here is the solution I am using.

Use the 'this' of your controller

.controller('newController',['$scope',function($scope){
    var $this = this;
    $this.testMe = function(val){
        $scope.myVal = parseInt(val)+1;
    }
}]);

Here is the test:

describe('newDir', function(){
var svc, 
    $rootScope,
    $scope,
    $controller,
    ctrl;


 beforeEach(function () {
    module('myMod');
 });

 beforeEach(function () {
    inject(function ( _$controller_,_$rootScope_) {

        $controller = _$controller_;
        $rootScope = _$rootScope_;
        $compile = _$compile_;
        $scope = $rootScope.$new();
        ctrl = $controller('newController', {'$rootScope': $rootScope, '$scope': $scope });
    });
});

it('testMe inc number', function() {

    ctrl.testMe(10)
    expect($scope.myVal).toEqual(11);
});

});

Full Code Example

tmc
  • 324
  • 2
  • 5
  • Is there a reason you use `$this` or is it just because you dont like using variables such as `self`? – svassr Dec 22 '15 at 16:56
  • Are you asking why I named my variable "$this" rather than naming it "self"? There is no reason for my choice in variable names. "Self" could make code easier to read. – tmc Jan 02 '16 at 00:47
  • Yes that was the question indeed Thanks – svassr Jan 05 '16 at 16:05
  • +1, excellent solution. Also, a good read on `this` vs `$scope`: http://stackoverflow.com/a/14168699/3123195 – The DIMM Reaper Jun 08 '16 at 19:36
  • Thanks @TheDIMMReaper! Also thanks for the link, Good stuff. – tmc Jun 12 '16 at 05:02
9

As is you won't have access to those functions. When you define a named JS function it's the same as if you were to say

var handleSuccessOfAPI = function(){};

In which case it would be pretty clear to see that the var is only in the scope within the block and there is no external reference to it from the wrapping controller.

Any function which could be called discretely (and therefore tested) will be available on the $scope of the controller.

shaunhusain
  • 19,630
  • 4
  • 38
  • 51
  • 2
    Correct. Moreover, we should not be testing private functions. Generally, `init()` is unit tested with mock data which will make sure `handleSuccessOfAPI` or `handleFailureOfAPI` is invoked. Mocked objects/data should drive the path of execution. – dmahapatro Apr 07 '14 at 19:52
  • I see $scope as making the function/variable public (when comparing it to something like Java). I'm not overly familiar with unit testing in Java, but does the "not be testing private functions" something you normally do in Java? – Chris Apr 11 '15 at 19:38
  • Chris, I have limited experience writing tests in general as well, have written some JUnit tests but I'm by no means an expert in testing or anything. I imagine since the private functions aren't exposed when you instantiate an instance of an object you're still under the same constraint that you can only test those things that are publicly accessible. I think ultimately this makes sense since you're really concerned with a chunk of code completing a task in a sort of black box fashion when you write a test, that is if I supply x I expect y, but the implementation details aren't a concern. – shaunhusain Apr 11 '15 at 21:01
  • @shaunhusain , so according to this anwser, named functions which are not in scope cant be tested with jasmine ? – Alok Mishra May 23 '16 at 12:07
  • Yes believe that's the case. Best IMO if most of your logic is in factory or service objects but same thing applies since the tests running can only access public properties or methods of an object. – shaunhusain May 23 '16 at 17:36