6

How can I unit test different views for the below scenario

.state('app.sr.product.upload', {
            name: 'upload',
            url: '/upload',
            data: {
                tags: [],
                userCommunities: []
            },
            views: {
                "productView@app.sr.product": {
                    templateUrl: 'views/upload/upload.html',
                    controller: 'UploadCtrl',
                    controllerAs: 'ul'
                },
                "tags@app.sr.product.upload": {
                    templateUrl: 'views/tags/tags.html',
                    controller: 'TagsCtrl',
                    controllerAs: 'vm'
                },
                "UserCommunityPanel@app.sr.product.upload": {
                    templateUrl: 'views/user-community/user-community.html',
                    controller: 'UserCommunityCtrl',
                    controllerAs: 'ul'
                },
            }
        })
  • If my view is tags@app.sr.product.upload then how can I test that my controller is TagsCtrl, my controllerAs value is vm etc??

  • How can I unit test if my state is app.sr.product.upload then data.tags=[], data.userCommunities=[] etc.

I searched for lot of docs and tutorials but didnt get it .

Any help is appreciable. Thanks

shreyansh
  • 1,637
  • 4
  • 26
  • 46

3 Answers3

5

Try this on for size. I'm assuming you would be using jasmine for your tests, but the concept is the same for any testing framework.

When you run your test, first subscribe to the '$stateChangeSuccess' event and then navigate to that state. Once the event fires, check the toState values to see if they are what you expect them to be.

You can run the snippet to see the tests in action.

//write a unit test
describe('state changes', function() {
  beforeEach(module('app'));
  var $rootScope, $state;
  beforeEach(inject(function(_$rootScope_, _$state_) {
    // The injector unwraps the underscores (_) from around the parameter names when matching
    $rootScope = _$rootScope_;
    $state = _$state_;
  }));


  it('loads page 1', function(done) {
    //wait for the state to change, then make sure we changed to the correct state
    $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
      expect(toState.controller).toEqual('Controller1');
      done();
    });
    //navigate to the state
    $state.go('state1');
    //start a digest cycle so ui-router will navigate
    $rootScope.$apply();
  });

  it('loads page 2', function(done) {
    //wait for the state to change, then make sure we changed to the correct state
    $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
      expect(toState.controller).toEqual('Controller2');
      done();
    });
    //navigate to the state
    $state.go('state2');
    //start a digest cycle so ui-router will navigate
    $rootScope.$apply();
  });

  it('loads page 3', function(done) {
    //wait for the state to change, then make sure we changed to the correct state
    $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
      expect(toState.controller).toEqual('Controller3');
      done();
    });
    //navigate to the state
    $state.go('state3');
    //start a digest cycle so ui-router will navigate
    $rootScope.$apply();
  });
});

//set up some dummy controllers and some dummy states
angular.module('app', ['ui.router']).controller('Controller1', function() {
  this.message = 'Page 1';
}).controller('Controller2', function() {
  this.message = 'Page 2';
}).controller('Controller3', function() {
  this.message = 'Page 3';
}).config(function($stateProvider, $urlRouterProvider) {
  $urlRouterProvider.otherwise("/state1");

  $stateProvider.state('state1', {
    url: "/state1",
    controller: 'Controller1',
    controllerAs: 'vm',
    template: '<h1>{{vm.message}}</h1>'
  }).state('state2', {
    url: "/state2",
    controller: 'Controller2',
    controllerAs: 'vm',
    template: '<h2>{{vm.message}}</h2>'
  }).state('state3', {
    url: "/state3",
    controller: 'Controller3',
    controllerAs: 'vm',
    template: '<h3>{{vm.message}}</h3>'
  });
});
h1 {
  color: red;
}
h2 {
  color: blue;
}
h3 {
  color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>

<script src="
https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.js"></script>
<link rel="stylesheet" type="text/css" href="http://jasmine.github.io/2.0/lib/jasmine.css">
<script src="http://jasmine.github.io/2.0/lib/jasmine.js"></script>
<script src="http://jasmine.github.io/2.0/lib/jasmine-html.js"></script>
<script src="http://jasmine.github.io/2.0/lib/boot.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-mocks.js"></script>
<div ng-app="app">
  <a ui-sref="state1">State 1</a>
  <a ui-sref="state2">State 2</a>
  <a ui-sref="state3">State 3</a>
  <div ui-view></div>
</div>
TwitchBronBron
  • 2,783
  • 3
  • 21
  • 45
  • 1
    The problem with these tests is that they could produce false positives if the `$stateChangeSuccess` event handler functions are never called. – Shaun Scovil Mar 14 '16 at 21:51
  • 1
    @ShaunScovil, I don't think that they would produce false positives. Because they are using the done callback, jasmine will throw an error after a timeout if the done function isn't called. So yeah, the tests might take a long time to run, but jasmine will let you know that your test never said it was done. – TwitchBronBron Mar 15 '16 at 15:57
  • If for some reason the event handler function is not called, there is no expect() outside of the callback that would cause the test to fail. – Shaun Scovil Mar 15 '16 at 16:15
  • Why I am getting Error: Unexpected request: GET views/index/index.html No more request expected.. Error – shreyansh Mar 19 '16 at 10:27
  • Your router is probably trying to load the index page, which is most likely your default page. You can add an interceptor for that file and just ignore it. `$httpBackend.when('GET', 'views/index/index.html') .respond("

    I am the index

    ");`
    – TwitchBronBron Mar 19 '16 at 13:03
1

If I'm not wrong, I think we missed the point of the initial question, which was

if my view is tags@app.sr.product.upload then how can I test that my controller is TagsCtrl, my controllerAs value is vm etc??

and

How can I unit test if my state is app.sr.product.upload then data.tags=[], data.userCommunities=[] etc.

Here's how you can test these :

var $rootScope, $state, $injector, state;

beforeEach(inject(function(_$rootScope_, _$state_){
    $rootScope = _$rootScope_;
    $state = _$state_;
    state = $state.get('app.sr.product.upload');
}));

it('should have the correct data parameters', function () {

    expect(state.data.tags).toEqual('');
    expect(state.data.userCommunities).toEqual('');

});

it('should render the dashboard views with the right Controllers', function () {

    var product = state.views['productView@app.sr.product'];
    var tags= state.views['tags@app.sr.product.upload'];
    var userCommunity = state.views['UserCommunityPanel@app.sr.product.upload'];

    expect(product.templateUrl).toEqual('views/upload/upload.html');
    expect(product.controller).toEqual('UploadCtrl');
    expect(product.controllerAs).toEqual('ul');

    // etc...

});

Also, in newer angular versions, you can just declare your controller like so:

controller: 'UploadCtrl as vm'
eHx
  • 181
  • 2
  • 17
0

It's not something I would normally unit test. UI-Router itself is well covered by tests.

You'd do better with e2e (end-to-end) tests with Protractor. You simulate a click on a link, you expect url to be this, use expect number of elements in a list to be that etc.

But if you really need it:

  • locate root element of each view (f.e. by adding a specific class and using selectors)
  • you should be able to access scope and controller via angular.element wrapper methods
m1gu3l
  • 763
  • 1
  • 6
  • 19
  • Can you please be more clear , If you can provide some demo code or any example, it will be a great help – shreyansh Mar 11 '16 at 04:29