Silky微服务框架在线文档Silky微服务框架在线文档
首页
文档
配置
源码解析
博文
github
gitee
首页
文档
配置
源码解析
博文
github
gitee
  • 简介

    • silky 框架介绍
  • 入门

    • 名词解释
    • 快速开始
    • 脚手架
    • 微服务模块化架构的最佳实践 & 约定
    • 示例
  • 主机与模块

    • 主机
    • 网关
    • 模块
    • 插件
  • 网关与 HTTP

    • Swagger 文档
    • 性能分析(MiniProfiler)
    • 跨域(CORS)
    • 审计日志
  • 服务与 RPC

    • 应用服务和服务条目
    • rpc通信
    • websocket通信
    •  服务注册中心
    • 服务治理
  • 数据与缓存

    • EFCore 数据访问
    • 缓存
    • 分布式锁
  • 安全与认证

    • 身份认证与授权
    • 分布式事务
  • 基础设施

    • 依赖注入
    • 对象到对象的映射
    • 参数验证
    • 链路跟踪
    • 日志(Serilog)
    • 健康检查
    • 消息总线(MassTransit)
    • 单元测试与集成测试

概述

silky 框架通过 Silky.TestBase 模块为开发者提供了集成测试基础设施。与纯单元测试不同,集成测试会启动真实的 IoC 容器(Autofac),加载模块依赖,使得被测代码在接近真实运行环境的上下文中执行,能够验证服务间的依赖注入、配置读取、模块初始化等行为。

安装

<PackageReference Include="Silky.TestBase" Version="3.9.1" />
<!-- 使用 xUnit 测试框架 -->
<PackageReference Include="xunit" Version="2.x.x" />
<PackageReference Include="Shouldly" Version="4.x.x" />  <!-- 可选的断言库 -->

核心类型

SilkyIntegratedTest<TStartupModule>

SilkyIntegratedTest<TStartupModule> 是所有集成测试类的基类,泛型参数 TStartupModule 是测试专用的模块入口类。

构造时,基类会:

  1. 创建宿主环境(默认环境名称为 Testing)
  2. 读取测试项目下的配置文件(appsettings.json、appsettings.Testing.json 等)
  3. 构建 Autofac 容器,完整执行模块初始化流程
  4. 暴露 ServiceProvider、Configuration、Engine 等属性供测试使用

构造完成后,可通过 GetRequiredService<T>() 获取已注册的服务实例。

成员说明
ServiceProvider根服务容器(IServiceProvider)
TestServiceScope测试用服务作用域(IServiceScope)
Configuration测试环境的 IConfiguration
EngineSilky 引擎实例(IEngine)
GetRequiredService<T>()从测试作用域中获取服务
BeforeAddApplication(services)钩子:在模块初始化前向容器追加注册
AfterAddApplication(services)钩子:在模块初始化后向容器追加注册
CreateConfigurationBuilder()钩子:自定义配置构建方式
CreateHostEnvironment()钩子:自定义测试宿主环境

当测试类析构时,Dispose() 会自动关闭 TestServiceScope,释放所有 Scoped 服务。

基本用法

步骤一:创建测试模块

测试模块通过 [DependsOn] 特性声明对被测业务模块的依赖:

using Silky.Core.Modularity;

[DependsOn(
    typeof(OrderApplicationModule),     // 被测业务模块
    typeof(AccountApplicationModule)    // 其他依赖模块
)]
public class OrderTestModule : SilkyModule
{
    // 可在此处重写 ConfigureServices、Initialize 等方法进行测试特定配置
}

步骤二:创建测试类

继承 SilkyIntegratedTest<TStartupModule>,在构造函数中获取被测服务:

using Silky.TestBase;
using Shouldly;
using Xunit;

public class OrderAppServiceTests : SilkyIntegratedTest<OrderTestModule>
{
    private readonly IOrderAppService _orderAppService;

    public OrderAppServiceTests()
    {
        _orderAppService = GetRequiredService<IOrderAppService>();
    }

    [Fact]
    public async Task CreateOrderAsync_ShouldReturnOrderId()
    {
        // Arrange
        var input = new CreateOrderInput
        {
            ProductId = 1,
            Quantity = 2,
            Address = "测试地址"
        };

        // Act
        var orderId = await _orderAppService.CreateOrderAsync(input);

        // Assert
        orderId.ShouldBeGreaterThan(0L);
    }

    [Fact]
    public async Task GetOrderAsync_WithInvalidId_ShouldThrowException()
    {
        await Should.ThrowAsync<UserFriendlyException>(
            () => _orderAppService.GetAsync(-1));
    }
}

步骤三:测试配置文件

在测试项目下添加 appsettings.json 或 appsettings.Testing.json,配置测试所需的数据库连接、服务地址等:

{
  "ConnectionStrings": {
    "Default": "Server=localhost;Database=TestDb;User Id=sa;Password=xxx;"
  }
}

提示

测试环境名称默认为 Testing,框架会按照 appsettings.json → appsettings.Testing.json 的顺序加载配置,测试专用配置会覆盖基础配置。

自定义测试基础设施

替换真实依赖为 Mock

通过重写 BeforeAddApplication 钩子,可以在模块加载前注入 Mock 对象,替换不便在测试环境中运行的外部依赖:

public class OrderAppServiceTests : SilkyIntegratedTest<OrderTestModule>
{
    protected override void BeforeAddApplication(IServiceCollection services)
    {
        // 用 Mock 替换外部支付服务
        var mockPaymentService = new Mock<IPaymentService>();
        mockPaymentService
            .Setup(s => s.ChargeAsync(It.IsAny<ChargeInput>()))
            .ReturnsAsync(new ChargeResult { Success = true });

        services.AddTransient(_ => mockPaymentService.Object);
    }
}

自定义宿主环境

重写 CreateHostEnvironment() 以自定义环境名称或 ContentRoot:

using Moq;
using Microsoft.Extensions.Hosting;

protected override IHostEnvironment CreateHostEnvironment()
{
    var mockEnv = new Mock<IHostEnvironment>();
    mockEnv.Setup(e => e.EnvironmentName).Returns("IntegrationTest");
    mockEnv.Setup(e => e.ApplicationName).Returns(GetType().Name);
    mockEnv.Setup(m => m.ContentRootPath).Returns(Directory.GetCurrentDirectory());
    return mockEnv.Object;
}

使用内存数据库

结合 EFCore 的 UseInMemoryDatabase,在内存中运行数据库操作:

[DependsOn(typeof(OrderApplicationModule))]
public class OrderTestModule : SilkyModule
{
    public override void ConfigureServices(IServiceCollection services)
    {
        // 将真实数据库连接替换为内存数据库
        services.Configure<DbContextOptions<OrderDbContext>>(options =>
        {
            options.UseInMemoryDatabase("TestOrderDb");
        });
    }
}

常见问题

问:集成测试是否会启动 RPC 服务?

不会。Silky.TestBase 只启动 IoC 容器,不会启动 Tcp 监听、注册中心连接等网络服务。如果被测服务包含需要 RPC 调用的路径,需要使用 Mock 替换相关依赖。

问:如何在测试中模拟已登录用户?

NullSession.Instance 的属性从 RpcContext 中读取,没有直接的 setter。在测试中,通过向 RpcContext 注入 Claim 附件来模拟已登录用户:

using System.Security.Claims;
using Silky.Core.Runtime.Rpc;

[Fact]
public async Task GetCurrentUserOrders_ShouldReturnUserOrders()
{
    // 模拟当前登录用户(通过 RpcContext 注入 Claim)
    RpcContext.Context.SetInvokeAttachment(ClaimTypes.NameIdentifier, 12345L);
    RpcContext.Context.SetInvokeAttachment(ClaimTypes.Name, "testuser");

    var orders = await _orderAppService.GetCurrentUserOrdersAsync();

    orders.ShouldNotBeEmpty();
}

问:集成测试的速度是否很慢?

首次构造测试类时需要初始化 IoC 容器,耗时通常在几百毫秒到几秒之间,取决于模块数量和初始化逻辑。可以利用 xUnit 的 IClassFixture<T> 在同一测试类的多个测试方法间共享容器实例,避免重复初始化:

public class OrderTestFixture : SilkyIntegratedTest<OrderTestModule> { }

public class OrderQueryTests : IClassFixture<OrderTestFixture>
{
    private readonly IOrderAppService _orderAppService;

    public OrderQueryTests(OrderTestFixture fixture)
    {
        _orderAppService = fixture.GetRequiredService<IOrderAppService>();
    }

    [Fact]
    public async Task GetOrderListAsync_ShouldReturnPagedResult() { ... }

    [Fact]
    public async Task GetOrderAsync_WithValidId_ShouldReturnOrder() { ... }
}
编辑当前页
Prev
消息总线(MassTransit)