使用 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&lt;InvalidOperationException&gt;(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 断言异常的主要内容,如果未能解决你的问题,请参考以下文章

Xunit常用断言整理&带一点Shouldly介绍

使用 xUnit 进行单元测试异常消息

无法使用 .NET Core 运行 xUnit 测试

无法使用 .NET Core 运行 xUnit 测试

xml 的流利断言

在 STA Thread WPF 下运行多个 xunit 测试时出现问题