【问题标题】:Assert an Exception using XUnit使用 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 不那么死板……

标签: c# unit-testing xunit


【解决方案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);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-20
          • 1970-01-01
          • 2018-09-26
          • 2020-10-25
          • 2018-08-21
          • 2020-03-13
          相关资源
          最近更新 更多