title: Testing the controller passed to an Angular Material Dialog instance
body: <p>First off, I am trying to unit test the controller that is being passed to an <a href="https://material.angularjs.org/latest/#/api/material.components.dialog/service/$mdDialog" rel="nofollow">Angular Material Dialog</a> instance.</p>
  
  <p>As a general question, does it make more sense to test such a controller separately, or by actually invoking<code>$mdDialog.show()</code>?</p>
  
  <p>I am attempting the first method, but I'm running into some issues, mostly related to how Angular Material binds the "locals" to the controller.</p>
  
  <p>Here is the code that I am using to invoke the dialog in my source code, which works as expected:</p>
  
  <pre><code>$mdDialog.show({
      controller: 'DeviceDetailController',
      controllerAs: 'vm',
      locals: {deviceId: "123"},
      bindToController: true,
      templateUrl: 'admin/views/deviceDetail.html',
      parent: angular.element(document.body),
      targetEvent: event
  });
  </code></pre>
  
  <p>I don't believe the <a href="https://material.angularjs.org/latest/#/api/material.components.dialog/service/$mdDialog#show" rel="nofollow">docs</a> have been updated, but as of version 0.9.0 or so, the locals are available to the controller at the time the constructor function is called (see <a href="https://github.com/angular/material/issues/1534#issuecomment-98536797" rel="nofollow">this issue on Github</a>). Here is a stripped-down version of the controller constructor function under test, so you can see why I need the variable to be passed in and available when the controller is "instantiated":</p>
  
  <pre><code>function DeviceDetailController(devicesService) {
      var vm = this;
  
      vm.device = {};
  //    vm.deviceId = null;           //this field is injected when the dialog is created, if there is one. For some reason I can't pre-assign it to null.
  
      activate();
  
      //////////
      function activate() {
          if (vm.deviceId != null) {
              loadDevice();
          }
      }
  
      function loadDevice() {
          devicesService.getDeviceById(vm.deviceId)
              .then(function(data) {
                  vm.device = data.collection;
              };
      }
  }
  </code></pre>
  
  <p>I am trying to test that the device is assigned to vm.device when a deviceId is passed in to the constructor function before it is invoked.</p>
  
  <p>The test (jasmine and sinon, run by karma):</p>
  
  <pre><code>describe('DeviceDetailController', function() {
      var $controllerConstructor, scope, mockDevicesService;
  
      beforeEach(module("admin"));
  
      beforeEach(inject(function ($controller, $rootScope) {
          mockDevicesService = sinon.stub({
              getDeviceById: function () {}
          });
          $controllerConstructor = $controller;
          scope = $rootScope.$new();
      }));
  
      it('should get a device from devicesService if passed a deviceId', function() {
          var mockDeviceId = 3;
          var mockDevice = {onlyIWouldHaveThis: true};
          var mockDeviceResponse = {collection: [mockDevice]};
          var mockDevicePromise = {
              then: function (cb) {
                  cb(mockDeviceResponse);
              }
          };
  
          var mockLocals = {deviceId: mockDeviceId, $scope: scope};
  
          mockDevicesService.getDeviceById.returns(mockDevicePromise);
  
          var ctrlConstructor = $controllerConstructor('DeviceDetailController as vm', mockLocals, true);
          angular.extend(ctrlConstructor.instance, mockLocals);
          ctrlConstructor();
  
          expect(scope.vm.deviceId).toBe(mockDeviceId);
          expect(scope.vm.device).toEqual(mockDevice);
      });
  });
  </code></pre>
  
  <p>When I run this, the first assertion passes and the second one fails ("Expected Object({ }) to equal Object({ onlyIWouldHaveThis: true })."), which shows me that deviceId is being injected into the controller's scope, but apparently not in time for the if clause in the activate() method to see it. </p>
  
  <p>You will notice that I am trying to mimic the basic procedure that <a href="https://github.com/angular/material/blob/master/src/core/services/compiler/compiler.js#L115" rel="nofollow">Angular Material</a> uses by calling $controller() with the third argument set to 'true', which causes <a href="https://github.com/angular/angular.js/blob/v1.4.5/src/ng/controller.js#L86" rel="nofollow">$controller()</a> to return the controller constructor function, as opposed to the resulting controller. I should then be able to extend the constructor with my local variables (just as Angular Material does in the code linked to above), and then invoke the constructor function to instantiate the controller.</p>
  
  <p>I have tried a number of things, including passing an isolate scope to the controller by calling <code>$rootScope.$new(true)</code>, to no effect (I actually can't say I fully understand isolate scope, but $mdDialog uses it by default).</p>
  
  <p>Any help is appreciated!</p>