Search Results for

    Show / Hide Table of Contents

    UnhandledExceptionHandling

    The UnhandledExceptionHandlingAttribute specifies how NUnit should handle unhandled exceptions that occur on background threads during test execution. By default, NUnit treats unhandled exceptions as test errors, but this attribute allows you to change that behavior.

    Note

    This attribute was introduced in NUnit 4.6 to address scenarios where background thread exceptions, particularly ThreadAbortException from Thread.Abort() calls, would cause tests to be incorrectly marked as cancelled.

    Warning

    This attribute only affects exceptions raised on threads other than the main test thread. Exceptions on the main test thread will always cause the test to fail, regardless of this attribute's setting.

    Handling Modes

    The attribute accepts an UnhandledExceptionHandling enum value:

    Mode Description
    Error Unhandled exceptions cause the test to fail (default behavior)
    Ignore Unhandled exceptions are ignored and do not affect the test result
    Default Same as Error

    Basic Usage

    Error Mode (Default)

    [Test]
    [UnhandledExceptionHandling(UnhandledExceptionHandling.Error)]
    public void TestWithErrorHandling()
    {
        // Any unhandled exception on background threads will cause this test to fail
        // This is the default behavior
        var task = Task.Run(() =>
        {
            // Work that completes successfully
            Thread.Sleep(10);
        });
        task.Wait();
        Assert.Pass();
    }
    

    Ignore Mode

    Use with caution - ignoring exceptions may hide real issues:

    [Test]
    [UnhandledExceptionHandling(UnhandledExceptionHandling.Ignore)]
    public void TestIgnoringBackgroundExceptions()
    {
        // Background exceptions are ignored - use with caution!
        // This test will pass even if background threads throw
        using var cts = new CancellationTokenSource();
        var task = Task.Run(() =>
        {
            // Simulate work
            Thread.Sleep(10);
        }, cts.Token);
    
        task.Wait();
        Assert.Pass();
    }
    

    Filtering by Exception Type

    You can specify which exception types should be handled differently:

    [Test]
    [UnhandledExceptionHandling(UnhandledExceptionHandling.Ignore, typeof(OperationCanceledException))]
    public void TestIgnoringSpecificExceptions()
    {
        // Only OperationCanceledException is ignored on background threads
        // Other exception types will still cause the test to fail
    
        _ = Task.Run(() =>
        {
            // This will throw on a background thread
            Thread.Sleep(10);
            throw new OperationCanceledException();
        });
    
        // The unhandled OperationCanceledException on the background thread
        // will be ignored by the attribute, so this test still passes
        Assert.Pass();
    }
    

    When exception types are specified:

    • Only the specified exception types are affected by the handling mode
    • Other exception types follow the default behavior (Error)
    • Multiple exception types can be specified

    Fixture-Level Application

    Apply to an entire fixture to affect all tests within it:

    [TestFixture]
    [UnhandledExceptionHandling(UnhandledExceptionHandling.Ignore)]
    public class LegacyCodeTests
    {
        // All tests in this fixture will ignore unhandled exceptions
        // on background threads
    
        [Test]
        public void TestLegacyComponent()
        {
            // Legacy code that might throw on background threads
            Assert.Pass();
        }
    
        [Test]
        public void TestAnotherLegacyComponent()
        {
            // This test also ignores background exceptions
            Assert.Pass();
        }
    }
    

    Common Scenarios

    Handling Thread.Abort Scenarios

    When testing code that uses Thread.Abort(), the ThreadAbortException thrown on the background thread can cause NUnit to mark the test as "cancelled by user" even though the test completed successfully. This attribute allows you to ignore ThreadAbortException specifically:

            [Test]
            [UnhandledExceptionHandling(UnhandledExceptionHandling.Ignore, typeof(ThreadAbortException))]
            public void TestWithThreadAbort()
            {
                // Thread.Abort() is not supported on .NET 5+
                // This example demonstrates the pattern for .NET Framework
                if (!IsThreadAbortSupported())
                {
                    Assert.Ignore("Thread.Abort() is not supported on this platform");
                }
    
                // This test uses Thread.Abort() which throws ThreadAbortException
                // Without this attribute, the test would be marked as "cancelled by user"
                var thread = new Thread(() => Thread.Sleep(1000));
                thread.Start();
                thread.Join(500);
    
    #pragma warning disable SYSLIB0006 // Thread.Abort is obsolete
                thread.Abort();
    #pragma warning restore SYSLIB0006
    
                Assert.Pass();
            }
    
            private static bool IsThreadAbortSupported()
            {
    #if NETFRAMEWORK
                return true;
    #else
                return false;
    #endif
            }
    
    Note

    Thread.Abort() is obsolete in .NET 5+ and throws PlatformNotSupportedException on those platforms. Consider migrating to CancellationToken-based cancellation patterns instead.

    Testing Fire-and-Forget Operations

    When testing code that intentionally spawns background work that might fail independently:

    [Test]
    [UnhandledExceptionHandling(UnhandledExceptionHandling.Ignore)]
    public void TestFireAndForgetLogging()
    {
        // The system under test fires off logging that we don't want to wait for
        var sut = new ServiceWithBackgroundLogging();
        var result = sut.DoWork();
    
        Assert.That(result, Is.EqualTo(expected));
        // Background logging exceptions won't fail this test
    }
    

    Testing Cancellation Scenarios

    When OperationCanceledException on background threads is expected and should be ignored:

    [Test]
    [UnhandledExceptionHandling(UnhandledExceptionHandling.Ignore, typeof(OperationCanceledException))]
    public async Task TestCancellationInBackgroundWork()
    {
        using var cts = new CancellationTokenSource();
    
        // Fire-and-forget background work that observes the cancellation token
        _ = Task.Run(() => LongRunningOperationAsync(cts.Token));
    
        // Trigger cancellation while the test continues
        cts.Cancel();
    
        // Perform other assertions that do not await the background work
        await Task.Delay(50);
        Assert.That(SomeResult(), Is.EqualTo(expected));
    
        // Any OperationCanceledException thrown on the background task
        // will be ignored by NUnit due to the UnhandledExceptionHandling setting
    }
    

    Legacy Code Integration

    When integrating with legacy code that has known background thread issues:

    [TestFixture]
    [UnhandledExceptionHandling(UnhandledExceptionHandling.Ignore)]
    public class LegacySystemTests
    {
        // Legacy system has known issues with background thread cleanup
        // that we accept for now
    }
    

    Inheritance and Scope

    The attribute can be applied at multiple levels:

    Level Scope
    Assembly Affects all tests in the assembly
    Class Affects all tests in the fixture
    Method Affects only the specific test

    When multiple levels specify the attribute, they are combined - each level's configuration is additive.

    Best Practices

    Warning

    Using Ignore mode can hide real bugs in your code. Use it sparingly and only when you understand the implications.

    1. Prefer Error mode - The default behavior ensures you're aware of all exceptions
    2. Be specific with exception types - When using Ignore, specify only the exception types you expect
    3. Document why - Add comments explaining why certain exceptions are being ignored
    4. Review regularly - Periodically review uses of Ignore mode to ensure they're still necessary

    See Also

    • Assert.Throws
    • Assert.ThrowsAsync
    • Edit this page
    In this article
    Back to top Generated by DocFX | Copyright (c) 2018- The NUnit Project - Licensed under CC BY-NC-SA 4.0