扩展方法,无法直接被 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);
}
}