Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 1.5 unit test controller that requires parent component's controller

I'm trying to unit test a (child) controller of an AngularJS 1.5 (with Webpack) component that requires a parent component and a controller from another module.

Child controller structure:

function ChildController () {
  var vm = this;

  vm.searchText = '';

  vm.submit = function() {
    var data = {};
    data['srch'] = vm.searchText;
    vm.parentCtrl.submitTextSearch(data);
  };
}

module.exports = ChildController;

Child component:

var template = require('./child.html');
var controller = require('./child.controller');

var childComponent = {
  require: {
    parentCtrl: '^parent'
  },
  template: template,
  controller: controller,
  controllerAs: 'vm'
};

module.exports = childComponent;

So what I would like to do is to mock out the parentCtrl that's required in the childController's submit()-function. I've been unable to find how to actually do this. I've found some similar child-parent directive solutions and tried those, e.g. injecting the parent controller through fake HTML-element as described in this child-parent directive example and basically the same stackoverflow solutions with no results. My problems differ at least in the fact that the child and parent controller are in different modules. And I suppose scope-tricks are not that much Angular 1.5-style?

The skeleton of my Jasmine test without my failed mock attempts:

describe('child component', function() {
  describe('child controller', function() {
    var controller;
    beforeEach(angular.mock.module('child'));
    beforeEach(inject(function(_$componentController_) {
      controller = _$componentController_('child');
    }))
    it('should work', function() {
      controller.searchText = "test";
      controller.submit();
    })
  })
})

That results in TypeError: Cannot read property 'submitTextSearch' of undefined. What exactly should I do to mock the parent controller out? With my limited experience in Angular, I'm out of ideas.

like image 698
profWoland Avatar asked Jun 29 '16 09:06

profWoland


3 Answers

In your case you're add parentCtrl as dependency of your component so in order to test it you have to mock parent component as well and assign it to controller. So you'd need to do something like:

beforeEach(inject(function(_$componentController_) {
  controller = _$componentController_('child');
  parentCtrl = _$componentController_('parent');
  controller.parentCtrl = parentCtrl;
}))
like image 194
asoriano Avatar answered Oct 23 '22 14:10

asoriano


Using the below code will get it initialized and please check the working Jasmine unit test Plunker

var ctrP = $componentController('parentComp');
var ctrl = $componentController('childComp', {}, {
  parentCtrl: ctrP
});

And your test case should be as shown below:

'use strict';

describe('component: heroDetail', function() {
  var $componentController, $compile, $rootScope;

  beforeEach(module('plunker'));
  beforeEach(inject(function(_$componentController_) {
    $componentController = _$componentController_;
  }));

  it('should expose a `hero` object', function() {
    var ctrP = $componentController('parentComp');
    console.log(ctrP);
    var ctrl = $componentController('childComp', {}, {
      parentCtrl: ctrP
    });
    console.log(ctrl);
    ctrl.submit('some data');
    expect(ctrl.parentCtrl.searchText).toEqual('some data');

  });
});
like image 1
raja reddy Avatar answered Oct 23 '22 14:10

raja reddy


1. Solution

In your test instantiate the parent controller with a new scope:

mainScope = $rootScope.$new();
$controller('ParentController', {$scope: mainScope});

and in your child controller, instantiate a new scope using the previously instantiated scope:

childScope = mainScope.$new();
$controller('ChildController', {$scope: childScope});

Example from the AngularJS documentation:

describe('state', function() {

  var mainScope, childScope, grandChildScope;

  beforeEach(module('myApp'));

  beforeEach(inject(function($rootScope, $controller) {
      mainScope = $rootScope.$new();
      $controller('MainController', {$scope: mainScope});
      childScope = mainScope.$new();
      $controller('ChildController', {$scope: childScope});
      grandChildScope = childScope.$new();
      $controller('GrandChildController', {$scope: grandChildScope});
  }));

  it('should work', function() {
      grandChildScope.searchText = "test";
      grandChildScope.submit();
  });
});

2. Solution

Child controller structure:

function ChildController () {
  var vm = this;

  vm.searchText = '';

  vm.submit = function() {
    var data = {};
    data['srch'] = vm.searchText;
    vm.parentCtrl.submitTextSearch(data);
  };
}

module.exports = ChildController;

Child component:

var template = require('./child.html');
var controller = require('./child.controller');

    var childComponent = {
      bindings: {
         searchText: 'test'
      },
      template: template,
      controller: controller,
      controllerAs: 'vm'
    };

module.exports = childComponent;

var ChildController = $componentController('childComponent', null, {...});
ChildController.$onInit();
expect(ChildController.searchText).to.equal('test');
expect(ChildController.submit()).to.equal('*expected result value should come here*');

REFRENCES :

AngularJS documentation - Testing Controllers

AngularJS documentation - $componentController

Unit Testing Angular Components with $componentController

like image 1
Varsha Avatar answered Oct 23 '22 16:10

Varsha