Модульное тестирование контроллеров с формами в AngularJS

У меня следующая ситуация с Angular:

Controller

function Controller () {
    // form used in template
    this.form ...
}

Шаблон, который имеет форму и использует этот контроллер

Template

<div ng-controller="Controller as ctrl">
    <form name="ctrl.form">
    ...
</div>

Я должен сказать, что я в общем смущен, почему Angular не имеет лучшего способа добавления формы в контроллер, кроме автоматического добавления this.form или же $scope.formВ зависимости от того, как вы используете контроллеры, но это, я думаю, другой вопрос.

Реальная проблема, с которой я столкнулся сейчас, заключается в том, что я не уверен, как мне это проверить. Если я просто создаю экземпляр контроллера в тесте, то моя форма не определена

$controller('Controller as ctrl')

Я нашел способ, просто скомпилировав шаблон

$scope = $rootScope.$new();
$compile(template)($scope);

Но потому что ng-controller в шаблоне начинается новая область, я не могу получить доступ к контроллеру напрямую с $scope.ctrlвместо этого я должен был бы сделать что-то вроде $scope.$$childHead.login

... и я чувствую, что это становится слишком сложно. edit: не говоря уже о том, что $$ указывает на частное свойство.

2 ответа

Я решил это сам, но я оставляю это здесь неприемлемым, потому что я не думаю, что решение очень хорошее. Если кто-нибудь знает лучший способ, пожалуйста, напишите.

Проблема с компиляцией шаблонов заключается в том, что также невозможно вставить фиктивные сервисы в контроллер, по крайней мере, я не понял этого. Вы получаете экземпляр контроллера в $scope, например $scope.ctrl, но это все.

Вторая попытка состояла в том, чтобы найти только форму в шаблоне, скомпилировать и добавить ее в контроллер, который был создан отдельно. Это сработало, но не совсем, потому что область действия $ для формы и контроллера была разной, и поэтому любое обновление поля не отражало состояние контроллера.

В конце концов, он работает, как создать экземпляр контроллера с $ scope

ctrl = $controller('Controller as ctrl', {
        someDependency: mockDependency,
        $scope: $scope
    });

а затем скомпилировать частичный шаблон (только форму) с той же $ scope

var formTpl = angular.element(template).find('form');
form = $compile(formTpl)($scope);

Таким образом, контроллер заканчивается в $scope.ctrl, но потому что форма уже названа name="ctrl.form" он вставляется в $scope.ctrl.form и это видно внутри контроллера.

Когда используешь controllerAsВы можете получить доступ к своей форме в тестах следующим образом:

// 1. Create a new scope
var $scope = $rootScope.$new();

// 2. Run the controller
var Controller = $controller("Controller as ctrl", { $scope: $scope });

// 3. Compile the template against our scope
// This will add property $scope.ctrl.form (assuming that form has name="ctrl.form")
$compile(angular.element(templateHtml))($scope);

// 4. Controller.form should now also be defined
expect(Controller.form).toBeDefined();

Между тем, насмешка может быть достигнута с помощью angular в $ обеспечить:

beforeEach(angular.mock.module(function ($provide) {
    function ApiServiceMock() {
        this.getName() = function () {
            return "Fake Name";
        };
    }

    // Provide our mocked service instead of 'ApiService' 
    // when our controller's code requests to inject it
    $provide.value("ApiService", ApiServiceMock);
}));

Полный пример (с использованием обоих: макет и компиляция форм с controllerAs):

Основной код приложения:

angular.module("my.module", [])

    .service("ApiService", function () {
        this.getName = function () {
            return "Real Name";
        };
    })

    .controller("Controller", function (ApiService) {
        var ctrl = this;
        ctrl.someProperty = ApiService.getName();
    });

HTML:

<div ng-controller="Controller as ctrl">
    <form name="ctrl.form">
        <input type="email" name="email" />
    </form>
</div>

Тестовое задание:

describe("Controller", function () {
    var $scope,
        Controller;

    beforeEach(angular.mock.module("my.module"));

    beforeEach(angular.mock.module(function ($provide) {
        function ApiServiceMock() {
            this.getName() = function () {
                return "Fake Name";
            };
        }

        $provide.value("ApiService", ApiServiceMock);
    }));

    beforeEach(inject(function ($rootScope, $controller, $compile) {
        $scope = $rootScope.$new();

        Controller = $controller("Controller as ctrl", { $scope: $scope });

        // FIXME: Use your own method of retrieving template's HTML instead 'getTemplateContents' placeholder
        var html = getTemplateContents("./my.template.html"),
            formTpl = angular.element(html).find('form');

        $compile(formTpl)($scope);
    }));

    it('should contain someProperty', function () {
        expect(Controller.someProperty).toBeDefined();
    });

    it('form should contain an email field', function () {
        expect(Controller.form.email).toBeDefined();
    });
});
Другие вопросы по тегам