Angular 1.5 unit test controller that requires parent component's controller
Asked Answered
S

3

11

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.

Saluki answered 29/6, 2016 at 9:5 Comment(0)
G
4

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;
}))
Guru answered 10/8, 2016 at 15:46 Comment(2)
I've tried this but doesn't work for me. Still getting "Controller '<parent>', required by directive '<child>', can't be found!".Roodepoortmaraisburg
Are you sure about your karma configuration ? Maybe some files are not being loaded.Guru
T
1

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

Threeply answered 7/3, 2017 at 4:35 Comment(0)
F
1

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');

  });
});
Frisby answered 7/3, 2017 at 5:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.