如何在"Throws"和"ThrowsAsync"中模拟异常类型



我想为一组不同的异常类型编写一个单元测试。我的常用方法是对一组特定的测试输入使用InlineAutoMoqData。

你可以在下面找到我的想法/希望的解决方案:

[Theory]
[InlineAutoMockData(typeOf(HttpRequestException)] 
[InlineAutoMockData(typeOf(InvalidOperationException)] 
public async void MyTestFunction_Should_HandleExceptions(Type exceptionType, Mock<HttpClient> clientMock)
{
// Arrange
var cut = CreateCut();
clientMock.Setup(c => c.SendAsync()).ThrowsAsync(new Exception()); // here I want to  create a generic Exception dependend on the exceptionType instead of "new Exception"
// Act
var result = cut.DoSomething();

// Assert
result.Should().BeTrue();
}

我可以添加一个开关箱检查"exceptionType"客户端依赖于它。这个解决方案有效,但感觉不太"正确"。我认为应该有一个更优雅的解决方案。

xUnit支持通用参数。所以你应该能够做这样的事情。

[Theory]
[MemberData(nameof(Exceptions))]
#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
public async Task MyTestFunction_Should_HandleExceptions<TException>(TException _, Mock<HttpClient> clientMock)
#pragma warning restore xUnit1026 // Theory methods should use all of their parameters
where TException : Exception, new()
{
// Arrange
var cut = CreateCut();
clientMock.Setup(c => c.SendAsync()).Throws<TException>();

// Act
var result = cut.DoSomething();
// Assert
result.Should().BeTrue();
}
public static TheoryData<Exception> Exceptions() => new()
{
new HttpRequestException(),
new InvalidOperationException()
};

由于我无法运行您的代码片段,下面是我用来验证上述想法是否有效的代码。

[Theory]
[MemberData(nameof(Exceptions))]
#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
public async Task MyTestFunction_Should_HandleExceptions<TException>(TException _)
#pragma warning restore xUnit1026 // Theory methods should use all of their parameters
where TException : Exception, new()
{
// Arrange
var handlerMock = new Mock<HttpMessageHandler>();
handlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>()
)
.Throws<TException>();
var client = new HttpClient(handlerMock.Object)
{
BaseAddress = new Uri("http://test.com/")
};
var cut = new Cut(client);
// Act
var result = await cut.DoSomething();
// Assert
result.Should().BeTrue();
}
public static TheoryData<Exception> Exceptions() => new()
{
new HttpRequestException(),
new InvalidOperationException()
};
private class Cut
{
private readonly HttpClient httpClient;
public Cut(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<bool> DoSomething()
{
var result = await httpClient.SendAsync(new HttpRequestMessage());
return result.IsSuccessStatusCode;
}
}

您可以使用Activator从类型创建对象。

clientMock.Setup(c => 
c.SendAsync()).ThrowsAsync((Exception) Activator.CreateInstance(exceptionType));

为什么不用[ClassData]呢?请参阅本文中的示例。

您需要像前面建议的那样使用反射,或者将测试放在抽象泛型类中,其中类型参数是异常类型,然后为您希望测试的每个异常类型添加派生类。不幸的是,这两种解决方案都不是很优雅。