20

I use John papa angular style guide my controller looks like:

following the style John papa style controller style guide:

function testController() {

    var vm = this;

    vm.model = { name: "controllerAs vm test" };
}

My testing code looks like:

describe('Controller: testController', function () {

    beforeEach(module('myApp'));

    var testController;

    beforeEach(inject(function ($controller) {
        scope = {};

        testController = $controller('testController', {
        });

    }));

    it('should have vm.model defined and testController.vm.model is equal to controllerAs vm test', function () { 
        expect(testController.vm).toBeDefined();  
        expect(testController.vm.model).toBeDefined();     
        expect(testController.vm.model.name).toEqual("controllerAs vm test");
    });
});

Result:

Test failed: Result Message: Expected undefined to be defined. at stack

So my question is how can we test vm.model and other variables from this? I have not found proper guide line in the guide lines: controllers

Sevle
  • 3,109
  • 2
  • 19
  • 31
Utpal Kumar Das
  • 1,226
  • 14
  • 21

3 Answers3

28

The vm is equal to the instance itself via vm = this;

Therefore, all the properties are hanging directly off of the object.

function foo(){
  var vm = this;

  vm.name = 'Josh';
}

var myFoo = new foo();
myFoo.name; // 'Josh';

So all you need to do is change your expectations to remove the vm property.

expect(testController).toBeDefined();  
expect(testController.model).toBeDefined();     
expect(testController.model.name).toEqual("controllerAs vm test");

In order to prove this, here is your exact example, and the associated Jasmine tests.

function testController() {

  var vm = this;

  vm.model = {
    name: "controllerAs vm test"
  };
}


angular.module('myApp', [])
  .controller('testController', testController);

describe('Controller: testController', function() {

  beforeEach(module('myApp'));

  var testController;

  beforeEach(inject(function($controller) {
    scope = {};

    testController = $controller('testController', {});

  }));

  it('should have model defined and testController.model.name is equal to controllerAs vm test', function() {
    expect(testController).toBeDefined();
    expect(testController.model).toBeDefined();
    expect(testController.model.name).toEqual("controllerAs vm test");
  });

  it('should not have a property called vm', function() {
    expect(testController.vm).toBeUndefined();
  });
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/boot.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.4/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.4/angular-mocks.js"></script>
Josh
  • 44,706
  • 7
  • 102
  • 124
  • as a function your answer looks ok but here my problem is when I initialize the controller instance by: testController = $controller('testController', { }); then testController.vm should work but does not work. So my question is to the guys who have worked with angular john papa's controller as vm syntax and tested with jasmine. – Utpal Kumar Das Feb 11 '15 at 14:44
  • @UtpalKumarDas - Under the hood `$controller` is just calling `new()`, so it works the same way. – Josh Feb 11 '15 at 14:46
  • 2
    @UtpalKumarDas - I have been working with Angular for several years now, and have done plenty of testing with Jasmine. I'm trying to explain that this isn't an Angular thing, but a JavaScript one. `vm` is not a property on the controller... it's a variable that is closed over by the function scope. Therefore it will **never** exist on your controller when used this way. `$controller` doesn't change how JS works. – Josh Feb 11 '15 at 14:48
  • 1
    yes now I understand vm=testControllerInstance so vm.variables are found in testControllerInstance. Thanks a lot for your answer. Great!! – Utpal Kumar Das Feb 11 '15 at 15:08
  • 2
    The answer Josh provided does work but I found it is not necessary to include: scope = {}; [Here](http://codepen.io/MAustinMMDP/pen/YXqqEz) is an example I created on Codepen. – MelissaMMDP Aug 19 '15 at 17:55
8

testController is the vm because you set var vm = this in the controller. So, to make your test code more similar to your controller code, you can try to set your controller to vm in the test too instead of testController

describe('Controller: testController', function () {
    // we work with "vm" instead of "testController" to have consistent verbiage
    // in test and controller
    var vm;

    beforeEach(module('app'));
    beforeEach(inject(function ($controller) {
        vm = $controller('testController', {}, {});
    }));

    it('should have vm.model defined and testController.vm.model is equal to controllerAs vm test', function () {

        // vm=this in controller
        expect(vm)
            .toBeDefined();

        // Testing primitives
        expect(vm.foo)
            .toBeDefined();
        expect(vm.foo)
            .toEqual('bar');

        // Testing objects
        expect(vm.model)
            .toBeDefined();
        expect(vm.model.name)
            .toEqual("Batman");

        // Testing a method
        expect(vm.greet())
            .toBeDefined();
        expect(vm.greet())
            .toEqual('Hello There');
    });
});

Code for the controller

(function () {
    'use strict';

    angular
        .module('app')
        .controller('testController', testController);

    /* @ngInject */
    function testController() {
        var vm = this;

        // Primitives
        vm.foo = 'bar';

        // Objects
        vm.model = {
            name: 'Batman'
        };

        // Methods
        vm.greet = function () {
            return 'Hello There';
        };
    }
})();

Hope this helps. Good Luck.

Aakash
  • 21,375
  • 7
  • 100
  • 81
1

I would create a new module and inject it as a dependency into the app module to make it simple and testable. Testing the controller with John Papa's Style Guide:

(function () {
  'use strict';

  angular
    .module('test')
    .controller('testController', testController);

  function testController() {
    var vm = this;
    vm.model = { name: "controllerAs vm test" };
  }
})();

The spec now would look like this:

'use strict';

describe('testController', function() {
  var testController;
  beforeEach(module('test'));

  beforeEach(function () {
    inject(function($injector) {
      testController = $injector.get('$controller')('testController', {});
    });
  });

  it('should have model defined and testController.name is equal to controllerAs vm test', function() {
    expect(testController).toBeDefined();
    expect(testController.name).toEqual("controllerAs vm test");
  });
});

Hope this helps anyone looking for similar solutions.

Andrew Lobban
  • 2,065
  • 2
  • 24
  • 38