dotnet / aspire

Tools, templates, and packages to accelerate building observable, production-ready apps
https://learn.microsoft.com/dotnet/aspire
MIT License
3.83k stars 460 forks source link

Is there way to get DashboardWebApplication or TracesViewModel service in aspire distributed testing? #4881

Open martasp opened 4 months ago

martasp commented 4 months ago

When I get the aspire TracesViewModel service from services it's null.

How can I get the aspire TracesViewModel service?

    [Fact]
    public async Task EndpointCall_ShouldGenerateDistributedTraces()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.AspireShop_AppHost>();
        await using var app = await appHost.BuildAsync();
        await app.StartAsync();

        // getting aspire TracesViewModel service, but tracesViewModel is null ?
        var tracesViewModel = app.Services.GetService<TracesViewModel>();
        //how can i get aspire TracesViewModel service?

        // Act
        var httpClient = app.CreateHttpClient("frontend");
        var response = await httpClient.GetAsync("/");

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotEmpty(tracesViewModel.GetTraces().Items);

        await app.StopAsync();
    }
martasp commented 4 months ago

I managed to get the TracesViewModel service by adding a hosted service DashboardWebApplication:

services.AddHostedService<DashboardWebApplication>();

Here is the full code snippet:

using System.Net;
using Microsoft.Extensions.DependencyInjection;
using Aspire.Dashboard;
using Aspire.Dashboard.Model;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Builder;
using System.Reflection;
using Microsoft.Extensions.Hosting;

namespace SamplesIntegrationTests.Works;

public class MyServiceConfigurator
{
    public void Configure(IServiceCollection services)
    {
    }
}
public static class ServiceCollectionExtensions
{
    public static object GetService(this IServiceProvider provider, Type serviceType, Type implementationType)
    {
        if (provider is null)
            throw new ArgumentNullException(nameof(provider));

        if (serviceType is null)
            throw new ArgumentNullException(nameof(serviceType));

        if (implementationType is null)
            throw new ArgumentNullException(nameof(implementationType));

        var service = provider.GetServices(serviceType)
                              .FirstOrDefault(s => s.GetType() == implementationType);

        if (service == null)
            throw new InvalidOperationException($"No service for type '{serviceType}' and implementation '{implementationType}' has been registered.");

        return service;
    }

    public static TService GetService<TService>(this IServiceProvider provider, Type implementationType) where TService : class
    {
        if (provider is null)
            throw new ArgumentNullException(nameof(provider));

        if (implementationType is null)
            throw new ArgumentNullException(nameof(implementationType));

        var service = provider.GetServices<TService>()
                              .FirstOrDefault(s => s.GetType() == implementationType);

        if (service == null)
            throw new InvalidOperationException($"No service for type '{typeof(TService)}' and implementation '{implementationType}' has been registered.");

        return service;
    }
}
public static class DashboardWebApplicationExtensions
{
    public static IServiceProvider GetAppServices(this DashboardWebApplication dashboardWebApplication)
    {
        // Use reflection to get the private _app field
        FieldInfo appField = typeof(DashboardWebApplication).GetField("_app", BindingFlags.NonPublic | BindingFlags.Instance);
        if (appField == null)
        {
            throw new InvalidOperationException("The DashboardWebApplication class does not contain a private field named '_app'.");
        }

        // Get the _app instance from the dashboardWebApplication instance
        WebApplication appInstance = appField.GetValue(dashboardWebApplication) as WebApplication;
        if (appInstance == null)
        {
            throw new InvalidOperationException("Unable to retrieve the WebApplication instance from the DashboardWebApplication instance.");
        }

        // Return the IServiceProvider from the WebApplication instance
        return appInstance.Services;
    }
}

public static class IDistributedApplicationTestingBuilderExtensions
{
    public static IServiceCollection AddDashboardWebApplication(this IServiceCollection services)
    {
        var loggerFactory = LoggerFactory.Create(builder =>
        {
            builder.AddConsole();
        });

        var myServiceConfigurator = new MyServiceConfigurator();
        var logger = loggerFactory.CreateLogger<DashboardWebApplication>();
        services.AddTransient(sp => logger);
        services.AddTransient<Action<IServiceCollection>>(sp => myServiceConfigurator.Configure);
        services.AddHostedService<DashboardWebApplication>();
        return services;
    }

    public static IServiceProvider GetDashboardWebApplication(this IServiceProvider services)
    {
        var dashboardWebApplication = (DashboardWebApplication)services.GetService<IHostedService>(typeof(DashboardWebApplication));
        var serviceProvider = dashboardWebApplication.GetAppServices();
        return serviceProvider;
    }

}

public class Works()
{
    [Fact]
    public async Task EndpointCall_ShouldGenerateDistributedTracesFails()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.AspireShop_AppHost>();
        await using var app = await appHost.BuildAsync();
        await app.StartAsync();

        // getting aspire TracesViewModel service, but tracesViewModel is null ?
        var tracesViewModel = app.Services.GetService<TracesViewModel>();
        //how can i get aspire TracesViewModel service?

        // Act
        var httpClient = app.CreateHttpClient("frontend");
        var response = await httpClient.GetAsync("/");

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotEmpty(tracesViewModel.GetTraces().Items);

        await app.StopAsync();
    }

    [Fact]
    public async Task EndpointCall_ShouldGenerateDistributedTracesWorks()
    {
        // Arrange
        var appHost = await DistributedApplicationTestingBuilder.CreateAsync<Projects.AspireShop_AppHost>();
        appHost.Services.AddDashboardWebApplication();
        await using var app = await appHost.BuildAsync();
        await app.StartAsync();

        var tracesViewModel = app.Services.GetDashboardWebApplication().GetService<TracesViewModel>();

        // Act
        var httpClient = app.CreateHttpClient("frontend");
        var response = await httpClient.GetAsync("/");

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotEmpty(tracesViewModel.GetTraces().Items);

        await app.StopAsync();
    }

}

Are there any better ways to get traces, and logs for testing?

martasp commented 4 months ago

Adding full poc repo: https://github.com/dotnet/aspire-samples/pull/361

davidfowl commented 1 month ago

This is a feature request. Ability to collect traces/structured logs/metrics in testing.