.NET6 Isolated Azure Function Unit and Integration Test causes gRPC exception - c#

I have an isolated Azure Function that makes couple of HTTP POST calls. I am trying to write an integration test for them. But the test setup fails with a gRPC error.
Here is the Program.cs that configures the HttpClient with services.AddHttpClient();
public class Program
{
public static void Main()
{
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddHttpClient();
}).Build();
host.Run();
}
}
The sample function looks like this:
public class AzFunctionEndpoint
{
public AzFunctionEndpoint(IHttpClientFactory httpClientFactory, IConfiguration configuration, ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<ResolveEndpoint>();
this.httpClientFactory = httpClientFactory;
this.configuration = configuration;
}
[Function("azfunction")]
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = "azs/azfunction")] HttpRequestData req)
{
// Couple of other HTTP calls using httpClientFactory
// return
var res = req.CreateResponse(HttpStatusCode.OK);
return res;
}
private readonly IConfiguration configuration;
private readonly IHttpClientFactory httpClientFactory;
private readonly ILogger logger;
}
The Function runs correctly on local machine and when deployed to Azure.
Now I try and create an integration test with
public class AzFunctionEndpointIntegrationTests
{
public AzFunctionEndpointIntegrationTests()
{
factory = new WebApplicationFactory<Program>();
var clientFactory = factory.Services.GetService<IHttpClientFactory>();
// THIS LINE CAUSES gRPC host error
client = clientFactory.CreateClient();
}
[Fact]
public async Task AzFunction_Should_BeOK()
{
// POST to the azure function
HttpRequestMessage request = new HttpRequestMessage(new HttpMethod(HttpMethods.Post), "api/azs/azfunction");
var response = await client.SendAsync(request);
response.StatusCode.Should().Be(System.Net.HttpStatusCode.OK);
}
private HttpClient client;
private WebApplicationFactory<Program> factory;
}
The test code that tries to create HttpClient to invoke my function causes this exception
client = clientFactory.CreateClient();
System.InvalidOperationException : The gRPC channel URI 'http://:51828' could not be parsed.
I don't quite understand what is this error ! Any help on writing integration tests for Azure Functions is appreciated.

System.InvalidOperationException : The gRPC channel URI
'http://:51828' could not be parsed.
I don't quite understand what is this error ! Any help on writing
integration tests for Azure Functions is appreciated.
Below are the few workaround may help to fix the above issue.
Based on this GitHub issue the root cause we have observed that, May be due to the usage of incorrect run/debug configuration the IDE you are using.
Please make sure that you are using the same as suggested on the given gitHub link to run the application.
To write an Integration test you can refer this Example for your function app.
Example of sample code:-
test.cs
[TestClass]
public class DefaultHttpTriggerTests
{
private HttpClient _http;
[TestInitialize]
public void Initialize()
{
this._http = new HttpClient();
}
[TestCleanup]
public void Cleanup()
{
this._http.Dispose();
}
[TestMethod]
public async Task Given_OpenApiUrl_When_Endpoint_Invoked_Then_It_Should_Return_Title()
{
// Arrange
var requestUri = "http://localhost:7071/api/openapi/v3.json";
// Act
var response = await this._http.GetStringAsync(requestUri).ConfigureAwait(false);
var doc = JsonConvert.DeserializeObject<OpenApiDocument>(response);
// Assert
doc.Should().NotBeNull();
doc.Info.Title.Should().Be("OpenAPI Document on Azure Functions");
doc.Components.Schemas.Should().ContainKey("greeting");
var schema = doc.Components.Schemas["greeting"];
schema.Type.Should().Be("object");
schema.Properties.Should().ContainKey("message");
var property = schema.Properties["message"];
property.Type.Should().Be("string");
}
}
For more information please refer the below links:-
SO THREAD:- System.InvaliOperationException: The gRPC channel URI 'http://0' could not be parsed .
MICROSOFT DOCUMENTATION:- Guide for running C# Azure Functions in an isolated process

Related

Setting up integration test project with custom WebApplicationFactory

I'm trying to setup a test project for my asp.net core web API. There are a couple of specialties I need to handle:
Use a different DB for the tests
Getting the UserManger to seed some users
According to the Documentation I should derive form WebApplicationFactory and override the ConfigureWebHost method. So I did:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
public BeepDbContext Context;
public UserManager<User> UserMgr;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
ServiceDescriptor descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<BeepDbContext>));
if (descriptor != null) services.Remove(descriptor);
services.AddDbContext<BeepDbContext>(o =>
o.UseSqlServer("Server=.\\SQLEXPRESS;Database=BeepTest;Trusted_Connection=true;"));
ServiceProvider provider = services.BuildServiceProvider();
using (IServiceScope scope = provider.CreateScope())
{
Context = scope.ServiceProvider.GetRequiredService<BeepDbContext>();
Context.Database.Migrate();
UserMgr = scope.ServiceProvider.GetRequiredService<UserManager<User>>(); // <--- fails here
}
});
}
}
and this is my test class:
public class UserControllerTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly ITestOutputHelper _output;
private readonly CustomWebApplicationFactory _factory;
public UserControllerTests(ITestOutputHelper output, CustomWebApplicationFactory factory)
{
_output = output;
_factory = factory;
}
[Fact]
public async Task Login()
{
HttpClient client = _factory.CreateClient();
HttpResponseMessage result = await client.PostAsJsonAsync("/api/auth/login", new UserForLoginDto()
{
Username = "user",
Password = "P#ssw0rd"
});
_output.WriteLine(result.StatusCode.ToString());
_output.WriteLine(result.Content.ReadAsStringAsync().Result);
Assert.Equal(HttpStatusCode.OK, result.StatusCode);
}
}
This fails on the line where I try to get the UserManager saying that there is no service registered for this type. Also I was unable to make it use the config file from the test project instead of the main project. Hence I hard coded the connection string. After a bit of Debugging I've found out that the ConfigureServices method from the startup Class is executed after the one in my override. So I think this explains why I can't get the User manager. What I don't understand is, what am I doing wrong or how is this done properly?
As far as I can tell I'm doing pretty much the same as in the Documentation or the sample app they provide in the Documentation.

Intermittent dependency injection failure

I am seeing the following intermittent exception locally when trying to resolve a service within a .net core 2.1 azure function app. It only seems to happen when multiple messages are being processed by the function concurrently and it only fails some of the messages.
'System.Private.CoreLib: Exception while executing function: FunctionOne. Microsoft.Extensions.DependencyInjection: Unable to resolve service for type 'Microsoft.Extensions.Configuration.IConfiguration' while attempting to activate 'XXX.Service2'.
When the Service is in the same project as the function then everything works fine. It is only when I move it into another project that this occurs. The other project I created is just a simple .net standard 2.0 project with just this service in and a reference to the Microsoft.Extensions.Configuration nuget.
I know that this implementation uses a Service Locator which is an anti pattern but I still want to understand why this exception occurs.
[FunctionName("FunctionOne")]
public static void Run(
[QueueTrigger(_queue, Connection = _storageConnection)]
string queueItem,
ILogger trace)
{
// Startup
var services = Startup.GetServices();
// Services
var service = services.GetService<IService2>();
}
public static class Startup
{
public static Func<IServiceProvider> GetServices = CreateServices;
public static IConfiguration GetConfiguration()
{
return new ConfigurationBuilder()
.AddEnvironmentVariables()
.Build();
}
private static IServiceProvider CreateServices()
{
var services = new ServiceCollection();
var config = GetConfiguration();
services
.AddSingleton(config)
.AddSingleton<IService2, Service2>();
return services.BuildServiceProvider();
}
}
public class Service2 : IService2
{
public Service2(IConfiguration configuration)
{
}
}
public interface IService2
{
}
Try injecting it as an IConfigurationRoot instead of IConfiguration:
public HomeController(IConfigurationRoot configuration
, IService2 service)
{
_mailService = service;
_to = configuration["emailAddress.Support"];
}
In this case, the line
services.AddSingleton(provider => Configuration);
is equivalent to
services.AddSingleton<IConfigurationRoot>(provider => Configuration);
because the Configuration property on the class is declared as such, and injection will be done by matching whatever type it was registered as. We can replicate this pretty easily, which might make it clearer:
try this and see if it helps.
It appears to be a 'feature' that was introduced with version 2.0.12408.0 of the runtime. It does not happen with 2.0.12382.0.

ASP.NET Core 2.2 Integration Testing DbContext Service not registered

Setup
Windows 10
Visual Studio Professional 2017 v15.9.9
ASP.NET Core 2.2
EF Core 2.2
Dapper
xUnit 2.4.1
Description
I'm using the WebApplicationFactory from the Microsoft.AspNetCore.Mvc.Testing package to setup my integration tests.
I've been following the official documentation to customise the web host configuration.
The SUT uses Dapper to query from the database, so I'm not using the In-Memory provider that ships with EF Core for this particular integration test.
My code for setting up the WebApplictionFactory is below:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseStartup<TStartup>()
.ConfigureServices(services =>
{
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database context
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var dbContext = scopedServices.GetRequiredService<MyDbContext>(); // <-- service not found
dbContext.Database.EnsureCreated();
new MyDbContextSeed()
.SeedAsync(dbContext)
.Wait();
}
});
}
}
Issue
The MyDbContext service isn't found, I understand why (I think) - because the ServiceProvider from my Startup.cs class hasn't built yet.
But the question is How can I access the services from my Startup class here?
For context, the Integration test looks like this:
public class MyAPITests
: IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public MyAPITests(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async Task Get_ItemAsync_WhenIdNotFound_ReturnsNotFoundStatusCode()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, "api/v1/item/0");
// Act
var response = await _client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
First, ConfigureServices can be used instead of UseStartup, not together. Second, you shouldn't create the scope and do the migration during ConfigureServices anyways, but rather after the Web Host is built, see here:
In older tutorials, you may see similar code in the Configure method
in Startup.cs. We recommend that you use the Configure method only to
set up the request pipeline. Application startup code belongs in the
Main method.
The only way to do this is not in the factory, but after the factory built it:
public class MyAPITests
: IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public MyAPITests(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
var host = factory.Server.Host;
using (var scope = host.Services.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var dbContext = scopedServices.GetRequiredService<MyDbContext>();
dbContext.Database.EnsureCreated();
new MyDbContextSeed()
.SeedAsync(dbContext)
.Wait();
}
}
//...
}

How to use .NET Core's Built in Dependency Injection with Service Fabric

Good afternoon,
I recently started experimenting with Service Fabric and .NET Core.
I created a Stateless Web API and performed some DI using:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var connString = Configuration.GetConnectionString("DefaultConnection");
services.AddScoped<FaxLogic>();
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connString));
}
With the above I can use constructor inject on my FaxLogic class as well as my DbContext class (through the FaxLogic):
private readonly FaxLogic _faxLogic;
public FaxController(
FaxLogic faxLogic)
{
_faxLogic = faxLogic;
}
private readonly ApplicationContext _context;
public FaxLogic(ApplicationContext context)
{
_context = context;
}
I then created a non-Web API stateless service. I want to be able to access my FaxLogic and DbContext like in my WebAPI, but within the RunAsync method of the stateless service:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
// TODO: Replace the following sample code with your own logic
// or remove this RunAsync override if it's not needed in your service.
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(this.Context, "Hello!");
// do db stuff here!
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
}
I am wondering how I'd do it. I tried playing with the CreateServiceInstanceListeners() method and the Program.cs file where ServiceRuntime is is used to register but I can't seem to figure it out! Any help would be appreciated.
The solution has been already answered here: Set up Dependency Injection on Service Fabric using default ASP.NET Core DI container
In summary, you have to register the dependencies before you create a new instance of your stateless service and then create a factory method to resolve the dependencies:
i.e:
public static class Program
{
public static void Main(string[] args)
{
var provider = new ServiceCollection()
.AddLogging()
.AddSingleton<IFooService, FooService>()
.AddSingleton<IMonitor, MyMonitor>()
.BuildServiceProvider();
ServiceRuntime.RegisterServiceAsync("MyServiceType",
context => new MyService(context, provider.GetService<IMonitor>());
}).GetAwaiter().GetResult();
See the linked answer for more details.
TaeSeo,
I think what you are looking for is implemented in the project I am working on - CoherentSolutions.Extensions.Hosting.ServiceFabric.
In the terms of CoherentSolutions.Extensions.Hosting.ServiceFabric what you are looking for would look like:
private static void Main(string[] args)
{
new HostBuilder()
.DefineStatelessService(
serviceBuilder => {
serviceBuilder
.UseServiceType("ServiceName")
.DefineDelegate(
delegateBuilder => {
delegateBuilder.ConfigureDependencies(
dependencies => {
dependencies.AddScoped<FaxLogic>();
});
delegateBuilder.UseDelegate(
async (StatelessServiceContext context, FaxLogic faxLogic) => {
while (true) {
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(context, "Hello!");
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
});
})
})
.Build()
.Run();
}
If you have more questions feel free to ask or check out the project wiki
Hope it helps.

Serilog with .NET Core AWS Serverless Web API project

I have an AWS Serverless API application which is built with .NET Core (2.1). I have set this up with Serilog logging which uses the AWS Cloudwatch sink for it's logging destination. This project depends on a shared class library which is where the core logic resides.
I have noticed that not all my logging messages are making it into Cloudwatch. Messages in my API Controllers do get logged succesfully but messages from the shared class library are only logged when I'm running locally, not when using the live API.
Here is my logging setup in Startup.
var awsCredentials = new BasicAWSCredentials(config["AWS:AccessKey"], config["AWS:SecretKey"]);
if (env.IsProduction())
{
var options = new CloudWatchSinkOptions
{
LogGroupName = config["AWS:Cloudwatch:LogGroup"],
LogStreamNameProvider = new LogStreamNameProvider(),
TextFormatter = new Serilog.Formatting.Json.JsonFormatter()
};
var client = new AmazonCloudWatchLogsClient(awsCredentials, RegionEndpoint.EUWest2);
Log.Logger = new LoggerConfiguration()
.Enrich.WithExceptionDetails()
.WriteTo
.AmazonCloudWatch(options, client)
.CreateLogger();
}
services.AddSingleton(Log.Logger);
I've also tried adding services.AddLogging(builder => builder.AddSerilog()); but that hasn't helped.
I am using it like this:
public class TestService
{
private readonly ILogger logger;
public TestService(ILogger logger)
{
this.logger = logger;
}
public void TestMethod()
{
this.logger.Information("Test Message");
}
}
The default dependency injection framework has several limitations. Inside of a controller the dependencies will automatically load via placement in the constructor. However, in nested classes you have to pass the dependency itself, otherwise the framework will not automatically resolve. The framework works under the presumption of a composition root. In essence:
// Auto Resolved:
public class SampleController : Controller
{
private readonly ILogger logger;
public SampleController(ILogger logger) => this.logger = logger;
public IActionResult Index()
{
logger.Information("...");
..
}
}
// Will not auto resolve
public class SampleService
{
private readonly ILogger logger;
public SampleService(ILogger logger) => this.logger = logger;
public void SampleAction()
{
logger.Information("...");
..
}
}
For you to correctly implement the logger within the SampleService you would need to do something along these lines:
// Logger as property
Logger = serviceProvider.GetService<ILogger>();
var sampleService = new SampleService(Logger);
// Called directly passed
var sampleService = new SampleService(serviceProvider.GetService<ILogger>());
That has been my experience, otherwise it will fail to use the defined implementation you passed. There could be information I do not have that may also be affecting your code.

Categories