8

I have xf array: var xf = []; And I have a function is a element in this array and a function to use it:

$scope.checkEmailValid = function () {
  var result = false;
  Iif (xf.validateEmail($scope.email, '256')) {
    result = true;
  }
  return result;
};

xf.validateUsername = function (sText) {
  var isValid = false;
  do {
    //Check for valid string.
    isValid = typeof sText === 'string';
    if (!isValid) {
      break;
    }
    //Check that each special character does not exist in string.
    for (var i = 0; i < sText.length; i++) {
      if (xf.SPECIAL_CHARS.indexOf(sText.charAt(i)) !== -1) {
        isValid = false;
        break;
      }
    }
    if (!isValid) {
      break;
    }
  } while (false);
  return isValid;
};

But when I run my spec:

it ('checkEmail', function(){
  $controller('MyCtrl', { $scope: $scope });
  xf.validateUsername();
  spyOn(window,xf.validateUsername).and.callThrough();
});

It makes an error:

xf.validateUsername is not a function

How can I cover it?

3
  • is xf a property on $scope, a property on the controller, or just some free-form variable in the controller function body? Commented Aug 3, 2015 at 4:38
  • @Claies: xf is just a free-form variable in the controller body. Commented Aug 3, 2015 at 6:40
  • 1
    it's not really possible to test this in that manner. If it is not actually bound to $scope or exposed as a public property, it is only accessible in the function that it is defined, due to the way that JavaScript closures operate. Commented Aug 3, 2015 at 16:10

4 Answers 4

1

The xf variable is not acessible from the outside of the controller's scope (i.e. not accessible in the unit test files).

You must've done the following thing:

angular
  .module('myModule')
  .controller(function ($scope) {
    var xf = [];
    // etc.
  });

You could attach the xf variable to the MyController instance once Angular instantiates it:

angular
  .module('myModule')
  .controller(function ($scope) {
    this.xf = [];
    // etc.
  });

But that's not really a clean way of doing it. A better way (in my opinion) would be to create a factory:

angular
  .module('myModule')
  .factory('xfService', function () {
    var xf = [];

    function validateUsername(text) {
      // etc.
    }

    function get() {
      return xf;
    }

    return {
      get: get,
      validateUsername: validateUsername
    };
  });

Now, you can inject the factory in your controller to use xf:

angular
  .module('myModule')
  .controller(function ($scope, xfService) {
    // somewhere
    var isValid = xfService.validateEmail($scope.email, '256');
    // to get the values in the array
    var values = xfService.get();
  });

Finally, for the unit tests, it becomes really easy to test the validateEmail method.

describe('Unit tests - xfService', function () {
  var xfService;
  beforeEach(angular.module('myModule'));
  beforeEach(angular.inject(function (_xfService_) {
      xfService = _xfService_;
    });
  });

  describe('xfService.validateUsername', function () {
    it('should return a boolean value', function () {
      // for example
      expect(typeof xfService.validateUsername('test')).toBe('boolean');
    });
    // add more unit tests to check that the method works as expected
  });
});

You'll need to add the angular-mocks file to the Karma config.

Thanks to Paul Podlech and Claies for the hints in the comments/answers.

Sign up to request clarification or add additional context in comments.

Comments

0

I'm not sure to completely understand your question. But there are a few thinks i think you are doing wrong:

  • If xf it's a global variable you should mock it, since you are testing the controller, not the global variable.
  • If you want to check the real function of your global variable, go to the karma.conf.js file and add the js file path to the files option:

    files: [ ..., 'fx-script.js', ... ],

  • callThrough should be used before the actual function is invoked:

    it ('checkEmail', function(){ var ctrl = $controller('MyCtrl', { $scope: $scope }); spyOn(window, ctrl.xf.validateUsername).and.callThrough(); ctrl.xf.validateUsername(); });

I recommend you to separately test your controller, service, or global scripts, and add mocks whenever you need to inject a dependency or global variable, so if you can tell for sure which module/script is failing any time.

2 Comments

Hi @Matho. Yes, I'm testing controller. xf is local variable in this controller. var xf = [ ]. And function validateUsername() is in together in file with xf array. I've written your code, but it's make error: TypeError: Cannot read property 'validateUsername' of undefined
In that case the problem is that you should do is expose the validateUsername function in the ctrl.scope just like you are doing it with $scope.checkEmailValid. You can and should only test the public functions which are exposed by the controller. Example: If your controller haves a function A, function B in their scope, and the B function calls the function C, in that case you should test A and B and see the spected results.
0

you should move functionality in xf into separate service/factory. Then inject it in controller. That makes it pretty easy to mock it while testing.

Comments

0

Try this in the controller

var xf = this.xf = [];

and this in your test

it ('checkEmail', function(){
 var xf = $controller('MyCtrl', { $scope: $scope }).xf;
 spyOn(xf, 'validateUsername').and.callThrough();
 xf.validateUsername();
});

But you should realize that this exposes your xf object on the Controller as mentioned in the comment of Claies.

Comments

Your Answer

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.