namespace Dpz.Core.ServiceTest;

[TestFixture]
public class ServiceDependencyInjectionTest
{
    [Test]
    public void AddDefaultServicesRegistersGeneratedRepositoryServices()
    {
        var services = new ServiceCollection();
        new ConfigurationBuilder().Build();

        services.AddDefaultServices();

        AssertScopedRegistration<IAccountService, AccountService>(services);
        AssertScopedRegistration<MongoMessageOutboxStore>(services);
        AssertGeneratedScopedRegistration<IMongoMessageOutboxStore>(services);
        AssertScopedRegistration<HealthCheckService>(services);
        AssertGeneratedScopedRegistration<IHealthCheckService>(services);
        AssertGeneratedScopedRegistration<IAppOptionService>(services);
        Assert.That(
            System.Linq.Enumerable.Any(
                services,
                service =>
                    service.ServiceType == typeof(ISteamGameService)
                    && service.Lifetime == ServiceLifetime.Transient
            ),
            Is.True
        );
    }

    [Test]
    public void SourceGeneratorCreatesRobotsCacheMetadataForAppOptionService()
    {
        var metadataType = typeof(AppOptionService).Assembly.GetType(
            "Dpz.Core.Service.RepositoryServiceImpl.GeneratedAppOptionServiceCacheMetadata"
        );

        Assert.That(metadataType, Is.Not.Null);

        var buildRobotsCacheKey = metadataType!.GetMethod(
            "BuildGetRobotsAsyncCacheKey",
            System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static
        );

        Assert.That(buildRobotsCacheKey, Is.Not.Null);
        Assert.That(
            buildRobotsCacheKey!.Invoke(null, []),
            Is.EqualTo($"{typeof(AppOptionService).FullName}:GetRobotsAsync")
        );
    }

    [Test]
    public void SourceGeneratorCreatesCacheMetadataForArticleService()
    {
        var metadataType = typeof(ArticleService).Assembly.GetType(
            "Dpz.Core.Service.RepositoryServiceImpl.GeneratedArticleServiceCacheMetadata"
        );

        Assert.That(metadataType, Is.Not.Null);

        var defaultPrefix = metadataType!
            .GetField(
                "DefaultPrefix",
                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static
            )
            ?.GetRawConstantValue();
        Assert.That(defaultPrefix, Is.EqualTo(typeof(ArticleService).FullName));

        var methodTag = metadataType
            .GetMethod(
                "GetMethodTag",
                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static
            )
            ?.Invoke(null, [nameof(ArticleService.GetTopArticlesAsync)]);
        Assert.That(
            methodTag,
            Is.EqualTo(
                $"{typeof(ArticleService).FullName}:{nameof(ArticleService.GetTopArticlesAsync)}"
            )
        );
    }

    private static void AssertScopedRegistration<TImplementation>(IServiceCollection services)
    {
        Assert.That(
            System.Linq.Enumerable.Any(
                services,
                service =>
                    service.ServiceType == typeof(TImplementation)
                    && service.ImplementationType == typeof(TImplementation)
                    && service.Lifetime == ServiceLifetime.Scoped
            ),
            Is.True
        );
    }

    private static void AssertScopedRegistration<TService, TImplementation>(
        IServiceCollection services
    )
    {
        Assert.That(
            System.Linq.Enumerable.Any(
                services,
                service =>
                    service.ServiceType == typeof(TService)
                    && service.ImplementationType == typeof(TImplementation)
                    && service.Lifetime == ServiceLifetime.Scoped
            ),
            Is.True
        );
    }

    private static void AssertGeneratedScopedRegistration<TService>(IServiceCollection services)
    {
        Assert.That(
            System.Linq.Enumerable.Any(
                services,
                service =>
                    service.ServiceType == typeof(TService)
                    && service.ImplementationType?.Name.StartsWith("GeneratedCached") == true
                    && service.Lifetime == ServiceLifetime.Scoped
            ),
            Is.True
        );
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个使用 NUnit 测试框架编写的单元测试类,主要用于测试 依赖注入(DI)服务注册 的正确性。

整体结构

命名空间: Dpz.Core.ServiceTest
测试类: ServiceDependencyInjectionTest
测试框架: NUnit ([TestFixture] 和 [Test] 特性)

测试方法详解

1. AddDefaultServicesRegistersGeneratedRepositoryServices()

目的: 验证 AddDefaultServices() 扩展方法是否正确注册了各种服务。

验证内容:

  • IAccountServiceAccountService 注册为 Scoped 生命周期
  • MongoMessageOutboxStore 注册为 Scoped
  • IMongoMessageOutboxStore 注册为自动生成的 Scoped 服务(通过源代码生成器)
  • HealthCheckService 和其接口 IHealthCheckService 注册正确
  • IAppOptionService 注册为源生成的缓存服务
  • ISteamGameService 注册为 Transient 生命周期

2. SourceGeneratorCreatesRobotsCacheMetadataForAppOptionService()

目的: 验证源代码生成器是否为 AppOptionService 创建了缓存元数据类。

验证内容:

  • ✅ 确认生成了名为 GeneratedAppOptionServiceCacheMetadata 的类型
  • ✅ 该类型包含静态方法 BuildGetRobotsAsyncCacheKey
  • ✅ 该方法返回的缓存键格式为: "{FullTypeName}:GetRobotsAsync"

示例输出: "Dpz.Core.Service.AppOptionService:GetRobotsAsync"


3. SourceGeneratorCreatesCacheMetadataForArticleService()

目的: 验证源代码生成器为 ArticleService 创建的缓存元数据。

验证内容:

  • ✅ 确认生成了 GeneratedArticleServiceCacheMetadata 类型
  • ✅ 包含常量字段 DefaultPrefix,值为完整类型名称
  • ✅ 包含静态方法 GetMethodTag(methodName),返回格式为: "{TypeName}:{MethodName}"

示例输出: "Dpz.Core.Service.ArticleService:GetTopArticlesAsync"


辅助断言方法

AssertScopedRegistration<TImplementation>

验证某个实现类是否以 Scoped 生命周期注册(服务类型和实现类型相同)。

AssertScopedRegistration<TService, TImplementation>

验证接口-实现对是否以 Scoped 生命周期正确注册。

AssertGeneratedScopedRegistration<TService>

验证某个接口是否注册了以 "GeneratedCached" 开头的源生成实现类,且为 Scoped 生命周期。


关键技术点

  1. 依赖注入生命周期验证: Scoped、Transient
  2. 源代码生成器验证: 通过反射检查生成的类型和方法
  3. 缓存键生成策略: 使用 "{TypeFullName}:{MethodName}" 格式
  4. 元数据驱动设计: 为缓存功能自动生成元数据类

典型使用场景

这类测试通常用于:

  • ✅ 确保 DI 容器配置正确
  • ✅ 验证源生成器(Source Generator)输出
  • ✅ 防止服务注册遗漏或生命周期配置错误
  • ✅ 确保缓存基础设施正确生成
评论加载中...