280

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;
}
2
  • 2
    What do you mean by "is not working as expected"? (Also, please format your code more readably. Use the preview, and post when it looks how you'd want it to look if you were reading it.)
    – Jon Skeet
    Commented Jul 10, 2017 at 16:40
  • 6
    Hint: you're calling GetSettingsForUserID("") before you start calling Assert.Throws. The Assert.Throws call can't help you there. I'd suggest being less rigid about AAA...
    – Jon Skeet
    Commented Jul 10, 2017 at 16:41

5 Answers 5

455

The Assert.Throws expression will catch the exception and assert the type. You are however calling the method under test outside of the assert expression and thus failing the test case.

[Fact]
public void ProfileRepository_GetSettingsForUserIDWithInvalidArguments_ThrowsArgumentException()
{
    //arrange
    ProfileRepository profiles = new ProfileRepository();
    // act & assert
    Assert.Throws<ArgumentException>(() => profiles.GetSettingsForUserID(""));
}

If bent on following AAA you can extract the action into its own variable.

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

Note how the exception can also be used for more detailed assertions

If testing asynchronously, Assert.ThrowsAsync follows similarly to the previously given example, except that the assertion should be awaited,

public async Task Some_Async_Test() {

    //...

    //Act
    Func<Task> act = () => subject.SomeMethodAsync();

    //Assert
    var exception = await Assert.ThrowsAsync<InvalidOperationException>(act);

    //...
}
0
80

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.

5
  • 1
    FWIW, This solution is great if you need to maybe validate the exception message, etc. I think that's when you might use Record.Exception.
    – Jeff LaFay
    Commented Apr 9, 2019 at 16:10
  • 2
    @JeffLaFay I appreciate I'm a bit late to the party here, how would that differ from using var exception = Assert.Throws<InvalidOperationException>(testCode); and asserting on exception.Message? or is it just another flavor of achieving the same thing?
    – ColinM
    Commented Nov 21, 2019 at 18:26
  • 1
    See other answer for how to use Record.ExceptionAsync for an async case.
    – LosManos
    Commented Mar 2, 2021 at 12:32
  • 2
    Great solution if you need to assert an exception is thrown but you don't want to or can't be specific about the exception's type.
    – DhyMik
    Commented Jul 21, 2022 at 13:23
  • @ColinM I'm even later to the show but this approach allows to clearly distinguish between Act and Assert and to be able to assert on properties specific to the exception type (as opposed to those available on the Exception type)
    – vc 74
    Commented Feb 27, 2023 at 15:00
10

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

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

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);
}
3
  • 1
    If you are reading this, do not write tests like this. Follow the protocols, they are there for a reason. Try catch blocks should be used to treat exceptions, not to test. Even if you don't want to use a library, you should use var ex = Assert.Throws<TException>(action); Commented Jul 4, 2022 at 23:08
  • 1
    It is there for a reason, can you please highlight the reason? Everything is there for a reason and there are alternatives ways of writing the same code. It works well here and makes no difference. Correct me if I am wrong, with a reason.
    – Sujoy
    Commented Jul 6, 2022 at 2:00
  • 1
    There are frameworks designed to mock test cases and assert certain results, such as throw an exception and assert that it was thrown. Try catch blocks aren't meant for testing purposes, they're meant for treating exceptions on execution time. If you wanna throw exceptions an then assert that they were thrown you should use the designed frameworks, such as Moq and FluentAssertions. Commented Aug 2, 2022 at 19:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.