Overview
This implementation verifies whether a specific exception occurs as expected when a method is executed. Using the Assert.Throws<T> method in xUnit.net, a test succeeds if the specified exception is thrown within the process. It fails if no exception is thrown or if a different type of exception occurs. This feature is essential for verifying argument checks and error-handling logic.
Specifications (Input/Output)
- Input: The process expected to throw an exception (Action delegate).
- Output: The thrown exception object (type
T). You can use this to further verify message content or other details. - Criteria:
- Success: An exception of the specified type
Toccurs. - Failure: No exception occurs.
- Failure: An exception of a different type occurs.
- Success: An exception of the specified type
Basic Usage
// Expecting an ArgumentNullException
Assert.Throws<ArgumentNullException>(() =>
{
// Process that should throw an exception
target.DoSomething(null);
});
Full Code
This is a complete example that verifies not only the occurrence of an exception but also the parameter name using the returned exception object.
using System;
using Xunit;
namespace ExceptionTesting
{
public class ExceptionTests
{
[Fact]
public void TestMethod_NullInput_ThrowsArgumentNullException()
{
var obj = new MySampleClass();
// Act & Assert
// 1. Verify that ArgumentNullException occurs
var ex = Assert.Throws<ArgumentNullException>(() =>
{
obj.TestMethod(null);
});
// 2. Further verify the exception content (optional)
// You can check which argument caused the error
Assert.Equal("inputObject", ex.ParamName);
}
[Fact]
public void TestMethod_ValidInput_NoException()
{
var obj = new MySampleClass();
// Confirm normal operation (no exception thrown)
// The test passes automatically if no exception occurs
obj.TestMethod(new object());
}
}
/// <summary>
/// Class to be tested
/// </summary>
public class MySampleClass
{
public void TestMethod(object inputObject)
{
if (inputObject == null)
{
// Throw an exception with the argument name
throw new ArgumentNullException(nameof(inputObject));
}
// Normal process...
}
}
}
Customization Points
- Allowing Derived Classes: If you want to allow exceptions that include inherited relationships, use
Assert.ThrowsAny<T>. For example,Assert.ThrowsAny<Exception>(() => ...)will pass if any exception occurs. - Verifying Exception Messages: By using the returned
exobject, you can test if the error message is appropriate.
var ex = Assert.Throws<InvalidOperationException>(...);
Assert.Equal("Operation cannot be performed.", ex.Message);
Points of Caution
- Minimize the Scope: It is a best practice to write only the single line that throws the exception inside the
Assert.Throwslambda expression. If the scope is too wide, you risk accidentally marking the test as successful due to an unexpected exception elsewhere (such as a setup error). - When No Exception Occurs: Since
Assert.Throwsexpects an exception, the test will fail if the process finishes normally. - Type Matching:
Assert.Throws<ArgumentException>does not catchArgumentNullException, even though it is a derived class. It requires an exact type match. If you need to consider inheritance, useThrowsAny.
Application
Testing Exceptions in Async Methods
For methods using async/await, use Assert.ThrowsAsync.
[Fact]
public async Task TestAsyncMethod_ThrowsException()
{
var obj = new MySampleClass();
// Use ThrowsAsync and await the result
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
await obj.RunAsync(null);
});
}
Summary
Assert.Throws<T> is the core of unit testing for error cases. To ensure that your code doesn’t just work correctly but also fails correctly (e.g., preventing processing with invalid arguments), always implement tests for null checks and validation logic.
