すべての ASP.NET Core コントローラーの依存関係の挿入をテストする方法は有効ですか?

次のように記述できます:

[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()}")));
        }
    }
}

このコードは実際には、データベース構成とスタートアップにないものを使用してasp.netコアアプリケーションをセットアップする完全なプロセスを実行するため、そこから派生させて、いくつかのものを削除/モックしたい場合があります。また、このコードには Microsoft.AspNetCore.TestHost nuget が必要です。

期待どおりに動作しなかったため、元のコードを変更しました。


@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;
        }
    }
}

https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/ から要約されています。詳細についてはリンクを参照してください。

ASP.NET 3.0 の時点で、ビルド時にコントローラーの依存関係を検証する方法があります:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .AddControllersAsServices(); // This part adds Controllers to DI

Program.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;
            });

注:

  • サービス プロバイダの検証は、デフォルトでは開発環境でのみ有効になっています。
  • 実行時の ServiceProvider ルックアップ (サービス ロケーター パターン) では機能しません。 _service = provider.GetRequiredService<MyService>();
  • [FromServices] では機能しません メソッド内のパラメーター (つまり、コンストラクターの依存関係のみをチェックします)
  • 「オープン ジェネリック」では機能しません。 services.AddSingleton(typeof(MyServiceWithGeneric<>));
  • ファクトリ関数で登録されたサービスでは機能しません。
    services.AddSingleton<MyService>(provider => 
    {
        var nestedService = provider.GetRequiredService<MyNestedService>();
        return new MyService(nestedService);
    });

No