0

I'm not being able to launch an Azure function locally programmatically inside an integration test.

The first I've read was:

programmatically-starting-function-app-is-failing-without-descriptive-output

But was of not help.

This is my function:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using AdmGroup.Omega.Framework.Net.Mail;
using Newtonsoft.Json;

namespace AdmGroup.Omega.Services.Infrastructure.EmailFunction.Functions;

public class EmailService
{
    private readonly IMailMessageSender mailMessageSender;

    public EmailService(IMailMessageSender mailMessageSender)
    {
        this.mailMessageSender = mailMessageSender;
    }

    [Function("EmailService")]
    public async Task<IActionResult> Run
    (
        [RabbitMQTrigger(queueName: "EmailQueue", ConnectionStringSetting = "admgroup:omega:services:infraestructure:emailservice:rabbitmq")] MailMessage email)
    {
        var strEmail = JsonConvert.SerializeObject(email);
        Console.WriteLine(strEmail);
        await mailMessageSender.SendAsync(email);

        return new OkResult();
    }
}

This is the Program.cs (where I configure dependency injection):

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using AdmGroup.Omega.Framework.EmailServicing.Services;
using AdmGroup.Omega.Framework.Net.Mail;
using AdmGroup.Omega.Framework.Security;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Azure.Identity;

var config = InitConfiguration();
var clientId = config["AzureKeyVault:ClientId"];
var keyVaultUrl = config["AzureKeyVault:KeyVaultUrl"];

if (!string.IsNullOrEmpty(clientId) && !string.IsNullOrEmpty(keyVaultUrl))
{
    var tokenCredential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = clientId });

    var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(s => s.AddSingleton<SecretClient>(_ => new(new Uri(keyVaultUrl), tokenCredential)))
    .ConfigureServices(s => s.AddSingleton<AzureKeyVaultSecretManager>())
    .ConfigureServices(s => s.AddSingleton<IMailMessageSender, Office365GraphEmailService>())
    .Build();

    await host.RunAsync();
}

/// <summary>
/// Allow test project to use an appsettings.json file.
/// Read RabbitMQ host and queue name from test appsettings
/// </summary>
/// <returns>Configuration</returns>
static IConfiguration InitConfiguration()
{
    return new ConfigurationBuilder()
       .AddJsonFile("local.settings.json")
        .AddEnvironmentVariables()
        .Build();
}

public partial class Program { }

If I run

func host start --dotnet-isolated-debug

from a PowerShell ("Azure Functions Core Tools" previously installed) the function just starts with no problem and I can then run the test, but the problem is that I need to launch the func from the test itself, that's the idea of test automation.

I can inject the AZ function class in the test and removing temporarily the function parameter for testing I was able to launch it with

emailService.Run()

and it works, I can also debug, but the problem is when the parameter is enabled again it complains about the lack of "MailMessage email" parameter in Run() that is what it will read from the RabbitMQ queue, so I'm stuck...

What can I try next?

Edit 1

I realized that in the end it's easy, I can just do Program.Main() from the test and it should just work, but the problem is doing that I get the following exception:

Exception has occurred: CLR/System.InvalidOperationException
An exception of type 'System.InvalidOperationException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'The gRPC channel URI 'http://:50852' could not be parsed.'
halfer
  • 19,824
  • 17
  • 99
  • 186
Diego Perez
  • 2,188
  • 2
  • 30
  • 58

1 Answers1

1

My 2 cents on Integration Tests like these.

I usually prefer using the closest to production setup for Integration Tests. For Azure Functions, the closest would be to use docker containers or deploy to Azure.

Instead of having this as part of your test code, it would be best to externalize that part using a script that deploys to Azure using an ARM Template or run a built docker container (which is much easier in most cases), and then run your tests against that.

While building your own host should work since Azure Functions is basically that, at best I would say its OK for Unit Testing if you need to test things with Dependency Injection for example but for an Integration Test, its not close enough to production for my preference.

PramodValavala
  • 6,026
  • 1
  • 11
  • 30