我是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;
}

断言。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);

    //...
}

如果你想严格遵守AAA级别,那么你可以使用Record。异常,以捕获Act阶段的异常。

然后,您可以在Assert阶段基于捕获的异常进行断言。

在xUnits测试中可以看到一个这样的例子。

[Fact]
public void Exception()
{
    Action testCode = () => { throw new InvalidOperationException(); };

    var ex = Record.Exception(testCode);

    Assert.NotNull(ex);
    Assert.IsType<InvalidOperationException>(ex);
}

这取决于您想要遵循的路径,xUnit提供的两种路径都完全支持。


如果你想坚持AAA级游戏,你可以考虑这样做:

// Act 
Task act() => handler.Handle(request);

// Assert
await Assert.ThrowsAsync<MyExpectedException>(act);

我个人认为有两种方法可以处理这种情况。假设我有下面的方法,我想测试

    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");

        }
    }

但我发现一个问题在这里,空白和空是两个不同的东西。 c#为空白提供了ArgumentException,为空引用提供了ArgumentNullException。

你可以这样重构你的代码

    public void GetSettingsForUserID(string userid)
    {
        Guard.Against.NullOrWhiteSpace(userid, nameof(userid));
    }

这里你需要阿达力。GuardClauses在你的代码项目和测试用例中是这样的

    [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*");
    }

与其遵循复杂的协议,我发现使用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);
}