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/