如何测试所有ASP.NET核心控制器依赖注入是否有效?

Mic*_*ker 5 c# automated-tests dependency-injection asp.net-core

我们偶尔会遇到一些问题,即某人将某些DI添加到控制器中,但忘记将相关行添加到Startup.cs中以设置对象的范围.

这不会阻止应用程序启动,而是在匹配相关端点时抛出异常.

是否有任何方式以编程方式检查所有控制器是否有效并阻止应用程序启动否则?

或者有一种简单的方法可以编写一个全能自动化测试来检查每个控制器是否可以使用Startup.cs中的指定DI进行实例化?

Mic*_*ker 8

总结自https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/,请参阅链接了解更多详细信息。

从 ASP.NET 3.0 开始,现在有一种方法可以验证构建时的控制器依赖关系:

启动.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddControllersAsServices(); // This part adds Controllers to DI
Run Code Online (Sandbox Code Playgroud)

程序.cs:

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .UseDefaultServiceProvider((context, options) =>
            {
                options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                // Validate DI on build
                options.ValidateOnBuild = true;
            });
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 默认情况下,服务提供商验证仅在开发环境中启用。
  • 不适用于运行时 ServiceProvider 查找(服务定位器模式),例如_service = provider.GetRequiredService<MyService>();
  • 不适用于[FromServices]方法中的参数(即它仅检查构造函数依赖项)
  • 不适用于“开放仿制药”,例如services.AddSingleton(typeof(MyServiceWithGeneric<>));
  • 不适用于通过工厂功能注册的服务,例如
    services.AddSingleton<MyService>(provider => 
    {
        var nestedService = provider.GetRequiredService<MyNestedService>();
        return new MyService(nestedService);
    });
Run Code Online (Sandbox Code Playgroud)


Raf*_*fal 6

你可以这样写:

[TestFixture]
[Category(TestCategory.Integration)]
public class ControllersResolutionTest
{
    [Test]
    public void VerifyControllers()
    {
        var builder = new WebHostBuilder()
            .UseStartup<IntegrationTestsStartup>();
        var testServer = new TestServer(builder);
        var controllersAssembly = typeof(UsersController).Assembly;
        var controllers = controllersAssembly.ExportedTypes.Where(x => typeof(ControllerBase).IsAssignableFrom(x));
        var activator = testServer.Host.Services.GetService<IControllerActivator>();
        var serviceProvider = testServer.Host.Services.GetService<IServiceProvider>();
        var errors = new Dictionary<Type, Exception>();
        foreach (var controllerType in controllers)
        {
            try
            {
                var actionContext = new ActionContext(
                    new DefaultHttpContext
                    {
                        RequestServices = serviceProvider
                    },
                    new RouteData(),
                    new ControllerActionDescriptor
                    {
                        ControllerTypeInfo = controllerType.GetTypeInfo()
                    });
                activator.Create(new ControllerContext(actionContext));
            }
            catch (Exception e)
            {
                errors.Add(controllerType, e);
            }
        }

        if (errors.Any())
        {
            Assert.Fail(
                string.Join(
                    Environment.NewLine,
                    errors.Select(x => $"Failed to resolve controller {x.Key.Name} due to {x.Value.ToString()}")));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码实际上经历了使用数据库配置设置 asp.net 核心应用程序的完整过程,以及你在启动时没有的东西,所以你可能想从中派生并删除/模拟一些东西。此代码还需要Microsoft.AspNetCore.TestHost nuget。


我更改了我提出的原始代码,因为它没有按预期工作。


sha*_*non 5

改编@Rafal 对 xUnit的回答以避免管理异常迭代并跳过对 TestHost 的依赖:

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Routing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace redacted.WebApi.Test {
    using Core;

    public class VerifyDependencies {
        [Theory]
        [MemberData(nameof(Controllers))]
        public void VerifyController(Type controllerType) {
            var services = new WebHostBuilder().UseStartup<Startup>().Build().Services;
            ControllerUtilities.Create(
                controllerType,
                services.GetService<IControllerActivator>(),
                services.GetService<IServiceProvider>()
            );
        }

        public static IEnumerable<object[]> Controllers() {
            return ControllerUtilities.GetControllers<ApiController>().Select(c => new object[] { c });
        }
    }

    public class ControllerUtilities {
        public static IEnumerable<Type> GetControllers<TProject>() {
            return typeof(TProject)
                .Assembly.ExportedTypes
                .Where(x => typeof(Controller).IsAssignableFrom(x));
        }

        public static Controller Create(Type controllerType, IControllerActivator activator, IServiceProvider serviceProvider) {
            return activator.Create(new ControllerContext(new ActionContext(
                new DefaultHttpContext {
                    RequestServices = serviceProvider
                },
                new RouteData(),
                new ControllerActionDescriptor {
                    ControllerTypeInfo = controllerType.GetTypeInfo()
                })
            )) as Controller;
        }

        public static TController Create<TController>(IControllerActivator activator, IServiceProvider serviceProvider) where TController : Controller {
            return Create(typeof(TController), activator, serviceProvider) as TController;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)