简体   繁体   English

测试传递给Angular Material Dialog实例的控制器

[英]Testing the controller passed to an Angular Material Dialog instance

First off, I am trying to unit test the controller that is being passed to an Angular Material Dialog instance. 首先,我试图对传递给Angular Material Dialog实例的控制器进行单元测试。

As a general question, does it make more sense to test such a controller separately, or by actually invoking $mdDialog.show() ? 作为一般性问题,单独测试这样的控制器,或者实际调用$mdDialog.show()会更有意义吗?

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. 我正在尝试第一种方法,但我遇到了一些问题,主要与Angular Material如何将“locals”绑定到控制器有关。

Here is the code that I am using to invoke the dialog in my source code, which works as expected: 这是我用来在源代码中调用对话框的代码,它按预期工作:

$mdDialog.show({
    controller: 'DeviceDetailController',
    controllerAs: 'vm',
    locals: {deviceId: "123"},
    bindToController: true,
    templateUrl: 'admin/views/deviceDetail.html',
    parent: angular.element(document.body),
    targetEvent: event
});

I don't believe the docs 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 this issue on Github ). 我不相信文档已经更新,但是从版本0.9.0开始,在调用构造函数时控制器可以使用本地文件(请参阅Github上的这个问题 )。 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": 这是一个被测试的控制器构造函数的精简版本,因此您可以看到为什么我需要传递变量并在控制器“实例化”时可用:

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;
            };
    }
}

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. 我试图测试在调用deviceId之前将deviceId传入构造函数时将设备分配给vm.device。

The test (jasmine and sinon, run by karma): 测试(茉莉和sinon,由业力运行):

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

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. 当我运行它时,第一个断言通过,第二个断言失败(“Expected Object({})”等于Object({onlyIWouldHaveThis:true})。“),这表明deviceId正被注入控制器的范围,但是显然没有及时使用activate()方法中的if子句来查看它。

You will notice that I am trying to mimic the basic procedure that Angular Material uses by calling $controller() with the third argument set to 'true', which causes $controller() to return the controller constructor function, as opposed to the resulting controller. 你会注意到我试图通过调用$ controller()并将第三个参数设置为'true'来模仿Angular Material使用的基本过程,这会导致$ 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. 然后,我应该能够使用我的局部变量扩展构造函数(就像Angular Material在链接到上面的代码中所做的那样),然后调用构造函数来实例化控制器。

I have tried a number of things, including passing an isolate scope to the controller by calling $rootScope.$new(true) , to no effect (I actually can't say I fully understand isolate scope, but $mdDialog uses it by default). 我已经尝试了很多东西,包括通过调用$rootScope.$new(true)将隔离范围传递给控制器$rootScope.$new(true) ,没有效果(我实际上不能说我完全理解隔离范围,但是$ mdDialog默认使用它)。

Any help is appreciated! 任何帮助表示赞赏!

The first thing I would try would be to lose the 'as vm' from your call to $controller. 我要尝试的第一件事就是从你对$ controller的调用中失去'as vm'。 You can just use the return value for your expect rather than testing scope. 您可以使用返回值作为期望而不是测试范围。

Try this: 试试这个:

var ctrlConstructor = $controllerConstructor('DeviceDetailController', mockLocals, true);
    angular.extend(ctrlConstructor.instance, mockLocals);
    var vm = ctrlConstructor();

    expect(vm.deviceId).toBe(mockDeviceId);
    expect(vm.device).toEqual(mockDevice);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM