Dev (Back & Front)ARTIGO

Compreendendo o Angular: injeção de dependências e testes

Não há nada de especial e difícil sobre injeção de dependência e testes com Angular. Você só precisa se acostumar com a sintaxe.

Quando eu comecei a aprender sobre testes de aplicações em AngularAngular30 conteúdosCriando Micro Frontends Angular com Native FederationDev (Back & Front) · mar 2026Angular v21 e o Fim da Cobertura de Testes Exclusivamente em TypeScript: Por Que Precisamos Repensar os Testes de ComponentesDev (Back & Front) · jan 2026Angular 8 é lançado com builder APIs e suporte web workerDev (Back & Front) · mai 2019Ver tudo em Dev (Back & Front) , eu achei surpreendentemente difícil de entender. Em particular, eu continuei preso na estranha sintaxe que era necessária para configurar as dependências de teste.

Como se vê, não há nada de especial e difícil sobre injeção de dependência e testes com Angular. Você só precisa se acostumar com a sintaxe. Uma maneira de fazer isso é fingir que é outra coisa. Permitam-me explicar.

Teste com Angular no modo fácil (mas inexistente)

Vamos imaginar que estamos escrevendo um aplicativo Pastry Shop. Nosso aplicativo precisa de um serviço para fazer bolos, que chamaremos de Baker. O serviço Baker requer algumas coisas para fazer o seu trabalho. Por exemplo, ele precisa ter acesso a uma Store (loja) onde se pode obter os ingredientes necessários. Ele também precisa de um Mixer (batedeira) e um Oven (forno). Em outras palavras, ele tem certas dependências.

Agora, vamos imaginar que estamos usando uma versão especial do JavaScript, que torna mais fácil expressar as dependências usando anotações. É assim que o nosso serviço Baker pode parecer:

@inject(Store, Mixer, Oven)
function Baker() {
    var bake = function(item) {
        if (item === 'vanilla cake') {
            var ingredients = [
                Store.get('sugar'),
                Store.get('flower'),
                Store.get('eggs')
            ];
            var dough = Mixer.mix(ingredients);
            var cake = Oven.cook(dough);   
            cake.frosting = 'vanilla';
            return cake;        
        };
    };
 
    return: {
        bake: bake
    };
};

Estamos usando uma anotação chamada @inject para especificar que Baker precisa de Store, Mixer e Oven para fazer o seu trabalho. Essa mesma anotação poderia ser usada para escrever os testes correspondentes:

@inject(Baker, Store, Mixer, Oven)
describe('Baker Service tests', function () {
    it('should bake vanilla cake', function () {
        // arrange
        spyOn(Store, 'get').andReturnValue({});
        spyOn(Mixer, 'mix').andReturnValue({});
        spyOn(Oven, 'cook').andReturnValue({});
        // act
        var cake = Baker.bake('cake');       
        // assert
        expect(Store.get).toHaveBeenCalledWith('sugar');
        expect(Store.get).toHaveBeenCalledWith('flower');
        expect(Store.get).toHaveBeenCalledWith('baking soda');
        expect(Store.get).toHaveBeenCalledWith('eggs');
        expect(Mixer.mix).toHaveBeenCalled();
        expect(Oven.cook).toHaveBeenCalled();
        expect(cake.frosting).toEqual('vanilla');
    });
});

Teste com Angular no modo (menos) fácil (mas realmente possível)

Infelizmente, o código acima não vai funcionar em JavaScript. A boa notícia é que o Angular nos oferece várias maneiras de fazer a mesma coisa:

  • Dependências implícitas – use os nomes dos parâmetros da função para indicar as dependências. Este método não funciona para código minimizado e deve, portanto, ser evitado.
  • $inject – configura a propriedade $inject em um objeto que requer dependências de um array de nomes de dependência.
  • Anotação de array em linha – transmite a lista de dependências como um array para a função constructor (como factory() ou controller()).

Dos três métodos acima descritos, o número 3 parece ser o mais aceito. Então, vamos reescrever o nosso código acima usando esse método. Primeiro, vamos dar uma olhada no serviço:

// simple, but not possible
@inject(Store, Mixer, Oven)
function Baker() {...}
 
// noisy, but possible
angular.module('PastryShop').factory('Baker', ['Store', 'Mixer', 'Oven',
    function(Store, Mixer, Oven) {...}
]);

Em seguida, vamos reescrever o teste:

// simple, but not possible
@inject(Store, Mixer, Oven)
describe('Baker Service tests', function () {
    it('should bake vanilla cake', function () {...});
});
 
// noisy, but possible
describe('Baker Service tests', function () {
    var Baker, Store, Mixer, Oven;
 
    beforeEach(angular.mock.module('PastryShop'));
 
    beforeEach(function () {
        angular.mock.inject(function (_Baker_, _Store_, _Mixer_, _Oven_) {
            Baker = _Baker_;
            Store = _Store_;
            Mixer = _Mixer_;
            Oven = _Oven_;
        });
    });
 
    it('should bake vanilla cake', function () {...});
});

Vamos entender o que está acontecendo aqui. O Angular fornece um módulo especial chamado ngMock, que expõe (entre outras coisas) dois métodos muito úteis:

  • module() – usado para iniciar seu aplicativo antes de cada teste.
  • inject() – usado para obter instâncias de vários serviços injetadas em seus testes. Por convenção, se você fizer um wrap no nome do serviço em underscores, o Angular vai retirar esses underscores antes de te dar o serviço certo.

Assim que obtivermos os serviços necessários do inject(), vamos simplesmente armazená-los no escopo do nosso bloco describe(), e eles se tornarão disponíveis para todos os nossos testes.

Uma nota sobre controllers

Existem algumas coisas a mais que temos que levar em consideração ao testar controllers. Em primeiro lugar, para realmente criar um controller para nossos testes, precisamos usar o serviço $controller. Em segundo lugar, uma vez que a maioria dos controllers precisam de um $scope para trabalhar, podemos obter um usando o método $rootScope do $new().

Aqui está o código:

describe('Baker Controller tests', function () {
    var scope, BakerController, Baker;
 
    beforeEach(angular.mock.module('PastryShop'));
 
    beforeEach(function () {
        angular.mock.inject(function ($rootScope,  $controller, _Baker_) {
            scope = $rootScope.$new();
            Baker = _Baker_;
 
            BakerController = $controller('BakerController', {
                $scope: scope,
                Baker: Baker
            });
        });
    });
});

Mais uma vez, estamos usando o método inject() para obter as nossas dependências, que nesse caso incluem $rootScope e $controller. Nós criamos uma instância de um $scope e o injetamos, juntamente com uma instância Baker, no BakerController.

***

Artigo traduzido pela Redação iMasters com autorização do autor. Publicado originalmente em http://tatiyants.com/groking-angular-dependency-injection-and-testing/

Gosta de escrever coisas engraças sobre tecnologia. É criador do JS.js e do movimento MoreSQL, além de inventor do Guilt Driven Development.

Ver perfil