entre - llamar funcion de otro controlador angularjs
Directivas de prueba que requieren controladores. (3)
Puedo pensar en dos enfoques:
1) Utilice ambas directivas
Supongamos que tenemos las siguientes directivas:
app.directive(''foo'', function() {
return {
restrict: ''E'',
controller: function($scope) {
this.add = function(x, y) {
return x + y;
}
}
};
});
app.directive(''bar'', function() {
return {
restrict: ''E'',
require: ''^foo'',
link: function(scope, element, attrs, foo) {
scope.callFoo = function(x, y) {
scope.sum = foo.add(x, y);
}
}
};
});
Para probar el método callFoo
, puede simplemente compilar ambas directivas y dejar que la bar
use la implementación de foo
:
it(''ensures callFoo does whatever it is supposed to'', function() {
// Arrange
var element = $compile(''<foo><bar></bar></foo>'')($scope);
var barScope = element.find(''bar'').scope();
// Act
barScope.callFoo(1, 2);
// Assert
expect(barScope.sum).toBe(3);
});
2) Mock foo''s controller out
Este no es muy sencillo y un poco complicado. Puedes usar element.controller()
para obtener el controlador de un elemento y simularlo con Jasmine:
it(''ensures callFoo does whatever it is supposed to'', function() {
// Arrange
var element = $compile(''<foo><bar></bar></foo>'')($scope);
var fooController = element.controller(''foo'');
var barScope = element.find(''bar'').scope();
spyOn(fooController, ''add'').andReturn(3);
// Act
barScope.callFoo(1, 2);
// Assert
expect(barScope.sum).toBe(3);
expect(fooController.add).toHaveBeenCalledWith(1, 2);
});
La parte difícil aparece cuando una directiva usa el controlador de la otra en su función de link
:
app.directive(''bar'', function() {
return {
restrict: ''E'',
require: ''^foo'',
link: function(scope, element, attrs, foo) {
scope.sum = foo.add(parseInt(attrs.x), parseInt(attrs.y));
}
};
});
En este caso, debe compilar cada directiva individualmente para poder simular la primera antes de que la segunda la use:
it(''ensures callFoo does whatever it is supposed to'', function() {
// Arrange
var fooElement = $compile(''<foo></foo>'')($scope);
var fooController = fooElement.controller(''foo'');
spyOn(fooController, ''add'').andReturn(3);
var barElement = angular.element(''<bar x="1" y="2"></bar>'')
fooElement.append(barElement);
// Act
barElement = $compile(barElement)($scope);
var barScope = barElement.scope();
// Assert
expect(barScope.sum).toBe(3);
expect(fooController.add).toHaveBeenCalledWith(1, 2);
});
El primer enfoque es mucho más fácil que el segundo, pero se basa en la implementación de la primera directiva, es decir, no estás probando cosas por unidad. Por otro lado, aunque burlarse del controlador de la directiva no es tan fácil, le da más control sobre la prueba y elimina la dependencia de la primera directiva. Por lo tanto, elegir sabiamente. :)
Por último, no soy consciente de una manera más fácil de hacer todo lo anterior. Si alguien sabe de un mejor enfoque, por favor, mejore mi respuesta.
Entonces vi otra pregunta: cómo simular un controlador de directivas requerido en la directiva UT, que es básicamente mi problema, pero parece que la respuesta a este hilo fue "cambia tu diseño". Quería asegurarme de que no hay manera de hacer esto. Tengo una directiva que declara un controlador que es usado por directivas de niños. Ahora estoy intentando escribir pruebas de jazmín para la directiva de niños, pero no puedo hacer que compilen las pruebas porque dependen del controlador. Esto es lo que parece:
addressModule.directive(''address'', [''$http'', function($http){
return {
replace: false,
restrict: ''A'',
scope: {
config: ''=''
},
template: ''<div id="addressContainer">'' +
''<div ng-if="!showAddressSelectionPage" basic-address config="config"/>'' +
''<div ng-if="showAddressSelectionPage" address-selector addresses="standardizedAddresses"/>'' +
''</div>'',
controller: function($scope)
{
this.showAddressInput = function(){
$scope.showAddressSelectionPage = false;
};
this.showAddressSelection = function(){
$scope.getStandardizedAddresses();
};
this.finish = function(){
$scope.finishAddress();
};
},
link: function(scope, element, attrs) {
...
}
}
}])
directiva infantil:
addressModule.directive(''basicAddress360'', [''translationService'', function(translationService){
return {
replace: true,
restrict: ''A'',
scope: {
config: ''=''
},
template:
''...'',
require: "^address360",
link: function(scope, element, attrs, addressController){
...
}
}
}])
prueba de jazmín:
it("should do something", inject(function($compile, $rootScope){
parentHtml = ''<div address/>'';
subDirectiveHtml = ''<div basic-address>'';
parentElement = $compile(parentHtml)(rootScope);
parentScope = parentElement.scope();
directiveElement = $compile(subDirectiveHtml)(parentScope);
directiveScope = directiveElement.scope();
$rootScope.$digest();
}));
¿No hay forma de que pruebe la subdirección con jazmín y, de ser así, qué me falta? Incluso si pudiera probar la directiva en sí sin las funciones del controlador, estaría feliz.
Teniendo en cuenta la ( fantástica ) respuesta de Michael Benford.
Si desea aislar completamente su controlador / directiva en su prueba, necesitará un enfoque ligeramente diferente.
3) burlándose completamente de cualquier controlador padre requerido
Cuando asocia un controlador con una directiva, una instancia del controlador se almacena en el almacén de datos del elemento. La convención de nomenclatura para el valor de clave es ''$'' + nombre de la directiva + ''Controlador'' . Cuando Angular intenta resolver un controlador requerido , atraviesa la jerarquía de datos utilizando esta convención para ubicar el controlador requerido. Esto se puede manipular fácilmente insertando instancias de controlador simuladas en elementos principales:
it(''ensures callFoo does whatever it is supposed to'', function() {
// Arrange
var fooCtrl = {
add: function() { return 123; }
};
spyOn(fooCtrl, ''add'').andCallThrough();
var element = angular.element(''<div><bar></bar></div>'');
element.data(''$fooController'', fooCtrl);
$compile(element)($scope);
var barScope = element.find(''bar'').scope();
// Act
barScope.callFoo(1, 2);
// Assert
expect(barScope.sum).toBe(123);
expect(fooCtrl.add).toHaveBeenCalled();
});
4) Método de enlace de separación
El mejor enfoque, en mi opinión, es aislar el método de enlace. Todos los enfoques anteriores realmente prueban demasiado y, cuando las situaciones se vuelven un poco más complejas que los simples ejemplos proporcionados aquí, requieren demasiada configuración.
Angular tiene el soporte perfecto para esta separación de preocupaciones:
// Register link function
app.factory(''barLinkFn'', function() {
return function(scope, element, attrs, foo) {
scope.callFoo = function(x, y) {
scope.sum = foo.add(x, y);
};
};
});
// Register directive
app.directive(''bar'', function(barLinkFn) {
return {
restrict: ''E'',
require: ''^foo'',
link: barLinkFn
};
});
Y cambiando nuestro beforeEach para incluir nuestra función de enlace ...:
inject(function(_barLinkFn_) {
barLinkFn = _barLinkFn_;
});
... podemos hacer:
it(''ensures callFoo does whatever it is supposed to'', function() {
// Arrange
var fooCtrl = {
add: function() { return 321; }
};
spyOn(fooCtrl, ''add'').andCallThrough();
barLinkFn($scope, $element, $attrs, fooCtrl);
// Act
$scope.callFoo(1, 2);
// Assert
expect($scope.sum).toBe(321);
expect(fooCtrl.add).toHaveBeenCalled();
});
De esta manera solo estamos probando las cosas que nos preocupan y se puede utilizar el mismo enfoque para aislar la función de compilación si es necesario.
5) Inyectar la definición de la directiva y burlarse de la función del controlador.
Otro enfoque es inyectar la definición de la directiva y simular lo que necesitemos. Lo mejor de esto es que puede escribir completamente las pruebas unitarias para la directiva de sus hijos sin depender de sus padres.
Al usar inyect () puede inyectar cualquier definición de directivas, proporcione el nombre de la directiva + ''Directiva'' y luego acceda a sus métodos y sustitúyalos cuando sea necesario.
it(''ensures callFoo does whatever it is supposed to'', inject(function(fooDirective) {
var fooDirectiveDefinition = fooDirective[0];
// Remove any behavior attached to original link function because unit
// tests should isolate from other components
fooDirectiveDefinition.link = angular.noop;
// Create a spy for foo.add function
var fooAddMock = jasmine.createSpy(''add'');
// And replace the original controller with the new one defining the spy
fooDirectiveDefinition.controller = function() {
this.add = fooAddMock;
};
// Arrange
var element = $compile(''<foo><bar></bar></foo>'')($scope);
var barScope = element.find(''bar'').scope();
// Act
barScope.callFoo(1, 2);
// Verify that add mock was called with proper parameters
expect(fooAddMock).toHaveBeenCalledWith(1, 2);
}));
La idea fue propuesta por Daniel Tabuenca en el grupo de Google AngularJS
En este Plunker Daniel se burla de la directiva ngModel.