Nimmo Nimmo - 3 months ago 8
AngularJS Question

Unit testing controllers in AngularJS (1.5), when those controllers are exported as modules (headache...)

So, I'm trying to run unit tests on a Controller in an Angular 1.5 application, so that I can do TDD for every other controller from now on. But, it's turning into a bit of a headache.

All of the tutorials and blog posts that talk about testing ng-controllers seem to do so by injecting the controller that their app uses, and injecting their actual app.

The application I'm building exports a function for its controllers, and I'm wondering if this is what's causing my headaches here.

Here's an example:

// Controller file
const myController = (app) => {
const controller = app.controller('MyCtrl', ['$scope', 'SomeService', ($scope, SomeService) => {
// Stuff in here

return controller

module.exports = {
init: myController

Where the above would be called with:


So, in my unit tests for this controller, I'm trying to do the setup in this manner:

// Test file
const myController = require('../path/to/myController')
const app = angular.module('testModule', [])

describe('Controller tests: ', () => {
it('should work', inject(($controller) => {
const testMyCtrl = $controller('MyCtrl', {
$scope: {}
assert.equal(true, true)

But, when I try and test this (using Karma and Mocha), I get:

Argument 'MyCtrl' is not a function, got undefined

Sadly I'm not all that familiar with unit testing in AngularJS, and I'm just struggling to join the dots here. Does anyone have any sage advice?


Okay, sorted this now. I've ended up amending my controller file so it looks like this:

// Controller file
const MyCtrl = ($scope, SomeService) => {
  // Stuff here

module.exports = myCtrl

Then I instantiate it in my app.js file like this:

import MyCtrl from './path/to/controller'
MyCtrl.$inject = ['$scope', SomeService] // Where 'SomeService' is also imported into this file
const app = angular.module('MyApp', [])
app.controller('MyCtrl', MyCtrl)

Then in my unit tests, I have a 'test-app.js' file that creates the test app and instantiates the services and controllers it needs in the same way (this is probably unnecessary really, it would work just as well using the real app, but it feels nicer to have them separated completely). Then in the file that tests my controller, I have;

// Controller's unit test file
describe('MyCtrl tests: ', () => {
  var myCtrl
  var $scope
  var SomeService

  beforeEach(angular.mock.module('testApp')) // testApp is created in test-app.js
  beforeEach(angular.mock.inject(($controller, $rootScope, _SomeService_) => {
    SomeService = _SomeService_
    $scope = $rootScope.$new()
    MyCtrl = $controller('MyCtrl', {
      $scope: $scope,
      SomeService: SomeService

  // tests...

And now all of my tests have access to the controller. Hopefully this will be of some use to someone in the future. :)