Fala pessoal, tudo bem? Vamos continuar a falar um pouco sobre Azure WebJobs. Este também vai ser um post bem simples e direto.
Há algum tempo eu tive a necessidade de testar o código do meu WebJob. Como vimos no post anterior, é possível executá-lo localmente, integrando todas as partes, para acompanhar sua execuação. Mas c’mon! Isso não é testar! Eu queria um teste isolado, teste de unidade, roots mesmo!
Invertendo o Controle (IoC) em Azure WebJobs
A primeira coisa que precisei fazer no meu WebJob foi deixar de instanciar as dependências no corpo da função a ser executada, e passá-las explicitamente para o construtor da classe contendo tais funções.
Imaginando um cenário onde temos um WebJob que faz o envio de emails usando um cliente SMTP, mudaríamos nosso código disso:
public class WebJobEnviadorDeEmails { public async Task ProcessEmailMessage([QueueTrigger("filaemails")] string message) { //construindo um objeto a partir da mensagem da fila var email = Email.APartirDaFila(message); await new EnviadorDeEmails().Enviar(Para: email.Para, Assunto: email.Assunto, Corpo: email.Corpo); } }
Para isso:
public class WebJobEnviadorDeEmails { private readonly IEnviadorDeEmails enviadorDeEmails; public WebJobEnviadorDeEmails(IEnviadorDeEmails enviadorDeEmails) { this.enviadorDeEmails = enviadorDeEmails; } public async Task ProcessEmailMessage([QueueTrigger("filaemails")] string message) { //construindo um objeto a partir da mensagem da fila var email = Email.APartirDaFila(message); await this.enviadorDeEmails.Enviar(Para: email.Para, Assunto: email.Assunto, Corpo: email.Corpo); } }
Com isso é responsabilidade de quem instancia a classe WebJobEnviadorDeEmails de passar uma instância de IEnviadorDeEmails. O problema é que quem cria essa classe é a infraestrutura de WebJobs do Azure. Como ensinar o Azure a resolver essa dependência?
Injeção de Dependências em WebJobs no Azure
É possível implementar uma classe que resolva dependências e “ensinar” o WebJob a usar esta classe. Uma das formas mais simples de fazer isso é utilizando um Container de IoC / DI. Pode ser algo simples como o Ninject mesmo, apenas para que não precisemos fazer isso manualmente. Vamos então criar uma classe que implemente IJobActivator:
public class MeuActivator : IJobActivator { private readonly IKernel ninject; public MeuActivator(StandardKernel ninject) { this.ninject = ninject; } public <T> CreateInstance<T>() => ninject.Get<T>(); }
O que fazemos nessa classe é implementar a interface IJobActivator
do namespace Microsoft.Azure.WebJobs.Host
. Esta interface possui apenas um único método que recebe um tipo genérico T e retorna uma instância de objeto deste tipo.
O que eu fiz de diferente foi passar uma instância de um kernel do Ninject para esta classe, assim o Ninject resolverá o objeto do tipo genérico para nós.
Agora o que precisamos fazer é ensinar nosso WebJob a usar esta classe, para isso vamos alterar nosso Program.cs desta forma:
class Program { static void Main() { var ninjectKernel = new StandardKernel(); ninjectKernel.Bind<IEnviadorDeEmails>().To<EnviadorDeEmailsFoo>(); var host = new JobHost(new JobHostConfiguration { JobActivator = new MeuActivator(ninjectKernel) }); host.RunAndBlock(); } }
As linhas 5 e 6 deste código simplesmente configuram o Ninject. Substitua isso para qualquer conteiner de IoC/DI que você estiver usando. A grande mágica aqui acontece nas linhas 8 e 10, onde instanciamos um JobHostConfiguration e definimos que o JobActivator é o que nós implementamos. Com isso sempre que a estrutura do WebJob instanciar nossa classe ela saberá como resolver as dependências que estiverem explícitas no construtor da mesma.
Agora que temos as dependências explícitas na classe que executa a tarefa do nosso WebJob, podemos partir para a escrita de testes de unidade. Assunto para um próximo post.
Abração,
Quaiats.
Créditos da imagem: https://csharp.christiannagel.com/
Vinicius Quaiato