使用 XUnit 断言异常
Posted
技术标签:
【中文标题】使用 XUnit 断言异常【英文标题】:Assert an Exception using XUnit 【发布时间】:2017-12-14 11:56:52 【问题描述】:我是 XUnit 和 Moq 的新手。我有一个将字符串作为参数的方法。如何使用 XUnit 处理异常。
[Fact]
public void ProfileRepository_GetSettingsForUserIDWithInvalidArguments_ThrowsArgumentException()
//arrange
ProfileRepository profiles = new ProfileRepository();
//act
var result = profiles.GetSettingsForUserID("");
//assert
//The below statement is not working as expected.
Assert.Throws<ArgumentException>(() => profiles.GetSettingsForUserID(""));
待测方法
public IEnumerable<Setting> GetSettingsForUserID(string userid)
if (string.IsNullOrWhiteSpace(userid)) throw new ArgumentException("User Id Cannot be null");
var s = profiles.Where(e => e.UserID == userid).SelectMany(e => e.Settings);
return s;
【问题讨论】:
“没有按预期工作”是什么意思? (另外,请格式化您的代码,使其更具可读性。使用预览,并在您阅读时发布您希望它的外观。) 提示:在您开始呼叫Assert.Throws
之前,您正在呼叫GetSettingsForUserID("")
。 Assert.Throws
电话无法帮助您。我建议对 AAA 不那么死板……
【参考方案1】:
Assert.Throws 表达式将捕获异常并断言类型。但是,您在断言表达式之外调用被测方法,因此测试用例失败。
[Fact]
public void ProfileRepository_GetSettingsForUserIDWithInvalidArguments_ThrowsArgumentException()
//arrange
ProfileRepository profiles = new ProfileRepository();
// act & assert
Assert.Throws<ArgumentException>(() => profiles.GetSettingsForUserID(""));
如果一心要遵循 AAA,您可以将操作提取到它自己的变量中。
[Fact]
public void ProfileRepository_GetSettingsForUserIDWithInvalidArguments_ThrowsArgumentException()
//arrange
ProfileRepository profiles = new ProfileRepository();
//act
Action act = () => profiles.GetSettingsForUserID("");
//assert
ArgumentException exception = Assert.Throws<ArgumentException>(act);
//The thrown exception can be used for even more detailed assertions.
Assert.Equal("expected error message here", exception.Message);
注意异常如何也可以用于更详细的断言
如果异步测试,Assert.ThrowsAsync 与前面给出的示例类似,除了应该等待断言,
public async Task Some_Async_Test()
//...
//Act
Func<Task> act = () => subject.SomeMethodAsync();
//Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(act);
//...
【讨论】:
【参考方案2】:如果您确实想对 AAA 保持严格,那么您可以使用 xUnit 中的 Record.Exception 在您的 Act 阶段捕获异常。
然后您可以根据在 Assert 阶段捕获的异常进行断言。
这方面的一个例子可以在xUnits tests 中看到。
[Fact]
public void Exception()
Action testCode = () => throw new InvalidOperationException(); ;
var ex = Record.Exception(testCode);
Assert.NotNull(ex);
Assert.IsType<InvalidOperationException>(ex);
您要遵循什么路径取决于您,并且 xUnit 提供的内容完全支持这两条路径。
【讨论】:
FWIW,如果您可能需要验证异常消息等,此解决方案非常棒。我认为您可能会使用 Record.Exception。 @JeffLaFay 我很感激我在这里聚会有点晚了,这与使用var exception = Assert.Throws<InvalidOperationException>(testCode);
和断言exception.Message
有什么不同?还是只是实现相同目标的另一种方式?
查看other answer 了解如何使用Record.ExceptionAsync
处理异步情况。【参考方案3】:
如果你想坚持 AAA,你可以考虑这样的事情:
// Act
Task act() => handler.Handle(request);
// Assert
await Assert.ThrowsAsync<MyExpectedException>(act);
【讨论】:
【参考方案4】:我认为有两种方法可以处理我个人喜欢的这种情况。假设我有以下要测试的方法
public class SampleCode
public void GetSettingsForUserID(string userid)
if (string.IsNullOrWhiteSpace(userid)) throw new ArgumentException("User Id
Cannot be null");
// Some code
我可以使用下面的测试用例进行测试,请确保在测试项目中添加 FluentAssertions nuget。
public class SampleTest
private SampleCode _sut;
public SampleTest()
_sut = new SampleCode();
[Theory]
[InlineData(null)]
[InlineData(" ")]
public void TestIfValueIsNullorwhiteSpace(string userId)
//Act
Action act= ()=> _sut.GetSettingsForUserID(userId);
// Assert
act.Should().ThrowExactly<ArgumentException>().WithMessage("User Id Cannot be null");
但我在这里发现了一个问题,空格和 Null 是两个不同的东西。 c# 为空格提供 ArgumentException,为空引用提供 ArgumentNullException。
所以你可以像这样重构你的代码
public void GetSettingsForUserID(string userid)
Guard.Against.NullOrWhiteSpace(userid, nameof(userid));
这里你需要在你的代码项目中使用 Ardalis.GuardClauses nuget 并且测试用例会是这样的
[Fact]
public void TestIfValueIsNull()
//Act
Action act = () => _sut.GetSettingsForUserID(null);
//Assert
act.Should().ThrowExactly<ArgumentNullException>().WithMessage("*userId*");
[Fact]
public void TestIfValueIsWhiteSpace()
//Act
Action act= ()=> _sut.GetSettingsForUserID(" ");
//Assert
act.Should().ThrowExactly<ArgumentException>().WithMessage("*userId*");
【讨论】:
【参考方案5】:我发现使用 try catch 块最方便,而不是遵循复杂的协议:
try
var output = Settings.GetResultFromIActionResult<int>(controller.CreateAllFromExternalAPI());
Assert.True(output > 0);
catch(InvalidOperationException e)
Assert.True("Country table can only be filled from ExternalAPI if table is blank"==e.Message);
【讨论】:
以上是关于使用 XUnit 断言异常的主要内容,如果未能解决你的问题,请参考以下文章