4

I'm currently trying to write tests for existing blocks of code and running into an issue with a controller that has a nested ng-grid inside of it. The issue comes from the controller trying to interact with the grid on initialization.

Testing Software
node@0.10.14
karma@0.10.2
karma-jasmine@0.1.5
karma-chrome-launcher@0.1.2

My Test:

define(["angularjs", "angular-mocks", "jquery",
    "js/3.0/report.app",
    "js/3.0/report.controller",
    "js/3.0/report.columns"
],
    function(angular, ngMocks, jquery, oARModule, oARCtrl, ARColumns) {
        "use strict";

        describe("Report Center Unit Tests", function() {
            var oModule;

            beforeEach(function() {
                oModule = angular.module("advertiser_report");
                module("advertiser_report");
            });

            it("Advertiser Report Module should be registered", function() {
                expect(oModule).not.toBeNull();
            });

            describe("Advertiser Report Controller", function() {
                var oCtrl, scope;

                beforeEach(inject(function($rootScope, $controller, $compile) {
                    var el = document.createElement('div');
                     el.setAttribute('ng-grid','gridOptions');
                     el.className = 'gridStyle';
                    scope = $rootScope.$new();
                    $compile(el)(scope);
                    oCtrl = $controller('ARController', {
                        $scope: scope
                    });
                }));

                it("Advertiser Report controller should be registered", function() {
                    expect(oCtrl).not.toBeNull();
                });
            });
        });
    });

You'll see where I've tried to create and compile an element with the ng-grid attribute. Without doing this I get the following error:

TypeError: Cannot read property 'columns' of undefined

Which is a result of the controller attempting to call things like
$scope.gridOptions.$gridScope.columns.each

So I added the creation of a div with ng-grid attribute, and got a new error:

TypeError: Cannot set property 'gridDim' of undefined

So, I tried to add scope.gridOptions before the $controller call, but this brought me back to the original error. I've been searching for way to make this work without rewriting the controller and/or templates, since they are currently working correctly in production.

Derokorian
  • 1,260
  • 1
  • 10
  • 16
  • Have you tried injecting your controller before compiling your grid? If the grid relies on things that are in the controller's scope it would make sense to me that the controller has to have a chance to put all that stuff in scope before the grid tries to access it. – c0bra Mar 20 '14 at 14:36
  • The grid does not access the controller, but the other way around. Which is why I'm trying to mock that dependency. – Derokorian Mar 21 '14 at 13:15
  • The grid accesses the 'gridOptions' scope property which is defined by the controller, correct? – c0bra Mar 21 '14 at 14:34
  • Right and if you read the end of my question, you notice I tried to add gridOptions to the scope property, and this brought me back to the original error. – Derokorian Mar 23 '14 at 17:56
  • Take a look at this plunker: http://plnkr.co/edit/uTC1d0?p=preview It has a working spec and a failing spec. The only difference is the order in which the controller is instantiated. If it's done before the grid is compiled, it works, because the controller has a chance to define the scope properties before the grid attempts to access them during $compile. – c0bra Mar 26 '14 at 16:00
  • We are refactoring the controller and rewriting it into multiple controllers, each with the ability to unit test in mind. FWIW here is a plunker which is exactly yours but with the addition that was causing problems: http://plnkr.co/edit/DBPiKEd3XpTHWHEjjyjX?p=preview – Derokorian Apr 01 '14 at 23:28
  • So... it's working for you now? It isn't working? Not sure if your plunker was just a source dump or if it's supposed to be functioning. – c0bra Apr 02 '14 at 16:54
  • Its YOUR plunker, with the added call as defined in our controller and my question showing that it fails with that addition and no other changes. NO it is _not_ working for me, but due to additional needs we are refactoring. – Derokorian Apr 03 '14 at 02:14
  • Of course it's going to fail. Your 'customGrid' directive isn't defined anywhere in the plunk. That's why I asked if you were actually wanting the plunk to run, or if you were just giving an example of what your DOM looked like. – c0bra Apr 04 '14 at 14:15

2 Answers2

2

Your (major!) problem here is that the controller is making assumptions about a View. It should not know about and thus not interact with ng-grid. Controllers should be View-independent! That quality (and Dependency Injection) is what makes controllers highly testable. The controller should only change the ViewModel (i.e. its $scope), and in testing you validate that the ViewModel is correct.

Doing otherwise goes against the MVVM paradigm and best practices.

If you feel like you must access the View (i.e. directives, DOM elements, etc...) from the controller, you are likely doing something wrong.

New Dev
  • 48,427
  • 12
  • 87
  • 129
1

The problem in the second Failing test is gridOptions and myData is not defined prior to the compilation. Notice the sequence of the 2 statements.

Passing oCtrl = $controller('MainCtrl', { $scope: $scope }); $compile(elm)($scope);

Failing

$compile(elm)($scope);
oCtrl = $controller('MainCtrl', { $scope: $scope });

In both cases you are trying to use the same html

elm = angular.element('<div ng-grid="gridOptions" style="width: 1000px; height: 1000px"></div>');

I suggest you get rid of

oCtrl = $controller('MainCtrl', { $scope: $scope });

maneuvers and use the following HTML element instead

elm = angular.element('<div ng-controller="MainCtrl" 
      ng-grid="gridOptions" style="width: 1000px; height: 1000px"></div>');

Notice ng-controller="MainCtrl".

So the end story is that you need gridOptions defined somewhere so that it ngGrid can access it. And make sure gridOptions dependent code in controller is deferred in a $timeout.

Also take a look at the slight changes in app.js

$timeout(function(){
    //your gridOptions dependent code
    $scope.gridOptions.$gridScope.columns.each(function(){
      return;
    });  
  });

Here is the working plnkr.

bhantol
  • 9,368
  • 7
  • 44
  • 81
  • Thanks! we've refactored how we're using ngGrid so its a bit of a moot point now, but I understand now why my tests weren't working so thank you for that. – Derokorian Sep 27 '14 at 16:38