Assert an Exception using XUnit

Viewed 171172

I am a newbie to XUnit and Moq. I have a method which takes string as an argument.How to handle an exception using 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(""));
}

Method under test

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;
}
5 Answers

If you do want to be rigid about AAA then you can use Record.Exception from xUnit to capture the Exception in your Act stage.

You can then make assertions based on the captured exception in the Assert stage.

An example of this can be seen in xUnits tests.

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

    var ex = Record.Exception(testCode);

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

It's up to you what path you want to follow, and both paths are fully supported by what xUnit provides.

You could consider something like this if you want to stick to AAA:

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

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

I think there are two way to handle this scenario which I personally like. Suppose I have below method which I want to test

    public class SampleCode
    {
       public void GetSettingsForUserID(string userid)
       {
          if (string.IsNullOrWhiteSpace(userid)) throw new ArgumentException("User Id 
             Cannot be null");
          // Some code 
       }
    }

I can test this with below testcase,Make sure you add FluentAssertions nuget in your test project.

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

        }
    }

but I found one problem here, whitespace and Null are two different things. c# provides ArgumentException for whitespace and ArgumentNullException for null reference.

So You can refactor your code something like this

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

Here you need Ardalis.GuardClauses nuget in your code project And testcase will be something like this

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

Instead of following complicated protocols I found it most convenient to use a try catch block :

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);
}
Related