FluentAssertions 异常断言技术详解
引言
在单元测试中,异常处理是一个非常重要的环节。FluentAssertions 提供了一套优雅且强大的异常断言机制,让开发者能够以流畅的方式验证代码是否按预期抛出异常。本文将全面介绍 FluentAssertions 中的异常断言功能。
基本异常断言
验证方法抛出特定异常
最基本的异常断言是验证某个方法是否抛出了特定类型的异常:
subject.Invoking(y => y.Foo("Hello"))
.Should().Throw<InvalidOperationException>()
.WithMessage("Hello is not allowed at this moment");
这段代码验证了 Foo
方法在被调用时会抛出 InvalidOperationException
,并且异常消息包含特定内容。
使用 Arrange-Act-Assert 模式
对于偏好 AAA 模式的开发者,可以使用以下方式:
Action act = () => subject.Foo2("Hello");
act.Should().Throw<InvalidOperationException>()
.WithInnerException<ArgumentException>()
.WithMessage("whatever");
这种方式不仅验证了异常类型,还验证了内部异常和消息内容。
高级异常验证
验证异常属性
FluentAssertions 允许对异常实例的属性进行详细验证:
var act = () => subject.Foo(null);
act.Should().Throw<ArgumentNullException>()
.WithParameterName("message");
使用 Where 进行自定义验证
对于更复杂的验证场景,可以使用 Where
方法:
var act = () => subject.Foo(null);
act.Should().Throw<ArgumentNullException>()
.Where(e => e.Message.StartsWith("did"));
通配符匹配异常消息
FluentAssertions 支持使用通配符匹配异常消息:
var act = () => subject.Foo(null);
act.Should().Throw<ArgumentNullException>()
.WithMessage("?did*");
支持的通配符包括:
*
:匹配零个或多个字符?
:匹配一个字符
验证不抛出异常
基本不抛出异常验证
var act = () => subject.Foo("Hello");
act.Should().NotThrow();
验证不抛出特定异常
var act = () => subject.Foo("Hello");
act.Should().NotThrow<InvalidOperationException>();
延迟验证
对于需要等待一段时间才能确定结果的场景:
var act = () => service.IsReady().Should().BeTrue();
act.Should().NotThrowAfter(10.Seconds(), 100.Milliseconds());
第二个参数指定了重试间隔时间。
处理 yield 返回的方法
对于使用 yield 返回集合的方法,需要使用 Enumerating
强制枚举:
Func<IEnumerable<char>> func = () => obj.SomeMethodThatUsesYield("blah");
func.Enumerating().Should().Throw<ArgumentException>();
或者:
obj.Enumerating(x => x.SomeMethodThatUsesYield("blah")).Should().Throw<ArgumentException>();
精确异常匹配
使用 ThrowExactly
和 WithInnerExceptionExactly
可以精确匹配异常类型:
// 会失败,如果抛出的是 ApplicationException 而不是 Exception
act.Should().ThrowExactly<Exception>();
异步异常处理
基本异步异常验证
var act = () => asyncObject.ThrowAsync<ArgumentException>();
await act.Should().ThrowAsync<InvalidOperationException>();
await act.Should().NotThrowAsync();
使用 Awaiting 方法
var act = asyncObject.Awaiting(x => x.ThrowAsync<ArgumentException>());
await act.Should().ThrowAsync<ArgumentException>();
异步延迟验证
await act.Should().NotThrowAfterAsync(2.Seconds(), 100.Milliseconds());
简化语法
使用 FluentActions
可以进一步简化代码:
FluentActions.Invoking(() => MyClass.Create(null))
.Should().Throw<ArgumentNullException>();
或者使用静态导入:
using static FluentAssertions.FluentActions;
Invoking(() => MyClass.Create(null))
.Should().Throw<ArgumentNullException>();
处理 AggregateException
FluentAssertions 会自动解包 AggregateException
,使得断言可以正常工作:
// 即使异常被包装在 AggregateException 中,也能正常工作
act.Should().Throw<InvalidOperationException>();
但 ThrowExactly
不会自动解包,它要求异常必须精确匹配。
总结
FluentAssertions 提供了全面而强大的异常断言功能,无论是同步还是异步代码,都能以流畅的方式进行验证。通过本文介绍的各种方法,开发者可以编写出更清晰、更健壮的单元测试代码。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考