Skip to content

扩展方法,无法直接被 Moq 拦截,因此我们需要模拟底层索引器(this[string key]

Fact、Theory区别

  • [Fact] 表示一个简单的、不需要输入数据的测试方法。

  • [Theory] 表示一个可重复运行的测试方法,并且可以使用不同的数据集进行测试。需要配合数据源(如 [InlineData][MemberData][ClassData] 等)为测试提供数据。

    csharp
    [Theory]
    [InlineData(2, 3, 5)]
    [InlineData(-1, -1, -2)]
    public void Add_ReturnsCorrectResult(int a, int b, int expected)
      
    public static IEnumerable<object[]> TestData => new List<object[]>
    {
        new object[] { 2, 3, 5 },
        new object[] { 0, 0, 0 },
        new object[] { -1, -1, -2 }
    };
    
    [Theory]
    [MemberData(nameof(TestData))]
    public void Add_ReturnsCorrectResult(int a, int b, int expected)
    
    public class TestDataClass : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { 2, 3, 5 };
            yield return new object[] { 0, 0, 0 };
            yield return new object[] { -1, -1, -2 };
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
    [Theory]
    [ClassData(typeof(TestDataClass))]
    public void Add_ReturnsCorrectResult(int a, int b, int expected)
    {
        int result = a + b;
        Assert.Equal(expected, result);
    }
    • 每个 [InlineData] 会生成一个测试用例
    • [MemberData]数据来自类的成员(如属性、方法或字段)
    • [ClassData]数据来自实现了 IEnumerable<object[]> 的类

示例

csharp
[Fact]
[Theory]
[AutoData]
public async Task SendGroupAtTextMessageAsync_Should_Success_Async(WxGroupAtTextEvent notification)
{
    // arrange
    var fixture = new Fixture();
    fixture.Customize(new AutoMoqCustomization());
    fixture.Customizations.Add(new IntegrationHttpClientMock());

    var api = fixture.Freeze<Mock<IWecomApi>>();
    api.Setup(p => p.SendGroupAtTextMessageAsync(It.IsAny<WecomGroupAtTextRequest>(), It.IsAny<string>()))
        .Returns(Task.FromResult(new WecomResponse()));

    //act
    var service = fixture.Create<WxGroupAtTextEventHandler>();
    await service.Handle(notification, CancellationToken.None);

    //assert
    Assert.True(true);
}

Mock数据

IHttpClientFactory

csharp
using AutoFixture.Kernel;

public class IntegrationHttpClientMock : ISpecimenBuilder
{
    public object? Create(object request, ISpecimenContext context)
    {
        if (request is not ParameterInfo pi)
            return new NoSpecimen();
        if (pi.ParameterType != typeof(IHttpClientFactory))
            return new NoSpecimen();

        var services = new ServiceCollection();
        services.AddHttpClient("QualityPlatformApi", config =>
        {
            config.BaseAddress = new Uri("http://www.istr.cn/");
        });
        services.AddHttpClient("GitLabApi", config =>
        {
            config.BaseAddress = new Uri("http://www.istr.cn/api/v4/");
            config.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
            config.DefaultRequestHeaders.Add("Private-Token", "RABfWZpinmixhWsyWpBq");
        });
       
        var scope = services.BuildServiceProvider().CreateScope();
        return scope.ServiceProvider.GetService<IHttpClientFactory>();
    }

    public static string HttpBaseAuthenticator(string username, string password)
    {
        var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password)));
        return $"Basic {token}";
    }
}
csharp
[Theory]
[AutoData]
public async Task SendGroupAtTextMessageAsync_Should_Success_Async(WxGroupAtTextEvent notification)
{
    // arrange
    var fixture = new Fixture();
    fixture.Customize(new AutoMoqCustomization());

    var api = fixture.Freeze<Mock<IWecomApi>>();
    api.Setup(p => p.SendGroupAtTextMessageAsync(It.IsAny<WecomGroupAtTextRequest>(), It.IsAny<string>()))
        .Returns(Task.FromResult(new WecomResponse()));

    //act
    var service = fixture.Create<WxGroupAtTextEventHandler>();
    await service.Handle(notification, CancellationToken.None);

    //assert
    Assert.True(true);
}

IConfiguration

  • Get<T>() 是一个扩展方法,无法被 Moq 模拟。
csharp
var config = _configuration.GetSection(DeptAuditorReplaceConfig.Position).Get<DeptAuditorReplaceConfig>();

fixture.Freeze<Mock<IConfiguration>>().SetupGet(_ => _["UatWorkFlowId"]).Returns("2332,5665");

var mockConfigurationSection = new Mock<IConfigurationSection>();
mockConfigurationSection.Setup(x => x["SonarQube:test"]).Returns("John");
var mockConfiguration = fixture.Freeze<Mock<IConfiguration>>();
mockConfiguration
    .Setup(x => x.GetSection(It.IsAny<string>()))
    .Returns(mockConfigurationSection.Object);

接口单测

csharp
public class WecomApi
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly ILogger<WecomApi> _logger;

    public WecomApi(IHttpClientFactory clientFactory, ILogger<WecomApi> logger)
    {
        _clientFactory = clientFactory;
        _logger = logger;
    }

    public async Task<WecomResponse?> SendGroupAtTextMessageAsync(WecomGroupAtTextRequest req, string traceId = "")
    {
        var url = "http://xxxx/wecom/open/wecom/message/group/at-text";
        var json = JsonConvert.SerializeObject(req);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        try
        {
            var client = _clientFactory.CreateClient();
            client.DefaultRequestHeaders.Add("token", "flight.netcore.techplatform");

            var response = await client.PostAsync(url, content);
            if (response.IsSuccessStatusCode)
            {
                var rspStr = await response.Content.ReadAsStringAsync();
                _logger.LogInfo($"发送讨论组At人消息 接口请求: {json}, 返回数据:{rspStr}", "SendGroupAtText", traceId);
                return JsonConvert.DeserializeObject<WecomResponse>(rspStr);
            }

            return null;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "发送讨论组At人消息异常", "SendGroupAtText", traceId);
            return null;
        }
    }
}
csharp
public class WecomApiTest
{
    [Theory]
    [AutoData]
    public async Task SendGroupAtTextMessageAsync_Should_Success_Async(WecomGroupAtTextRequest mergeRequest)
    {
        var fix = new Fixture();
        fix.Customize(new AutoMoqCustomization());
        fix.Customizations.Add(new IntegrationHttpClientMock());
        var psn = fix.Create<WecomApi>();
        var result = await psn.SendGroupAtTextMessageAsync(mergeRequest);
        Assert.NotNull(result);
    }
}