Automatically Test Public Methods Using Reflection, xUnit and FluentAssertions

How to Quickly Validate Outputs of All Methods in a .NET Core Project Even with Dependency Injection

Posted by Hüseyin Sekmenoğlu on December 14, 2024 Backend Development Testing Tooling & Productivity

In large .NET Core projects, manually writing unit tests for every method can be time-consuming. But what if you could automatically invoke all public methods, validate their outputs and ensure they don't throw exceptions even for classes that use dependency injection?

In this guide, we’ll show how to use:

  • Reflection to discover and invoke public methods

  • xUnit as the testing framework

  • FluentAssertions for expressive validations

  • Microsoft.Extensions.DependencyInjection to resolve dependencies


🧱 Prerequisites

Before starting, make sure your test project includes the following NuGet packages:

dotnet add package xunit
dotnet add package FluentAssertions
dotnet add package Microsoft.Extensions.DependencyInjection

🧪 The Core Idea

We will:

  • Scan all classes in the assembly

  • Instantiate them via DI if possible

  • Call their public methods with default parameters

  • Assert that each method does not throw and (optionally) returns non-null


🛠️ Sample Test Code

Here’s the full implementation:

using System;
using System.Linq;
using System.Reflection;
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

public class MethodApprovals
{
    private readonly IServiceProvider _serviceProvider;

    public MethodApprovals()
    {
        var services = new ServiceCollection();
        ConfigureServices(services);
        _serviceProvider = services.BuildServiceProvider();
    }

    private void ConfigureServices(IServiceCollection services)
    {
        // Register real or fake dependencies
        services.AddTransient<DependencyA>();
        services.AddTransient<DependencyB>();
        services.AddTransient<DependentClass>();
    }

    [Fact]
    public void ApproveAllMethodsInAssembly()
    {
        var assembly = Assembly.GetExecutingAssembly();

        foreach (var type in assembly.GetTypes())
        {
            if (!type.IsClass || type.IsAbstract || type.Namespace == null)
                continue;

            ApproveMethods(type);
        }
    }

    private void ApproveMethods(Type type)
    {
        var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                          .Where(m => !m.IsSpecialName); // Ignore property accessors

        foreach (var method in methods)
        {
            try
            {
                object instance = method.IsStatic
                    ? null
                    : _serviceProvider.GetService(type) ?? Activator.CreateInstance(type);

                var parameters = method.GetParameters()
                                       .Select(p => GetDefault(p.ParameterType))
                                       .ToArray();

                var result = method.Invoke(instance, parameters);

                result.Should().NotBeNull($"{type.FullName}.{method.Name} should return non-null");
            }
            catch (Exception ex)
            {
                ex.Should().BeNull($"{type.FullName}.{method.Name} should not throw an exception but it did: {ex}");
            }
        }
    }

    private object GetDefault(Type type)
    {
        return type.IsValueType ? Activator.CreateInstance(type) : null;
    }
}

📦 Example Classes Under Test

Let’s say your application has this simple service class:

public class DependencyA { }

public class DependencyB { }

public class DependentClass
{
    private readonly DependencyA _a;
    private readonly DependencyB _b;

    public DependentClass(DependencyA a, DependencyB b)
    {
        _a = a;
        _b = b;
    }

    public string SayHello() => "Hello from DependentClass!";
    public int Add(int x = 1, int y = 2) => x + y;
}

This will be picked up and tested automatically, including DI instantiation.


⚙️ When and Why to Use This

Great for smoke testing: You can quickly check that your services aren’t crashing with default values.

Works well for DTOs and services with light logic: Especially if they don’t need real data inputs.

⚠️ Not a replacement for proper unit tests: It doesn't validate business logic correctness just that methods execute cleanly with default inputs.


🧠 Tips for Real-World Use

  • Replace GetDefault(...) with AutoFixture if you want smart input generation.

  • Add filters to skip methods like Dispose() or those with required complex parameters.

  • Log or collect results instead of asserting, if you want a diagnostic report.


🧾 Conclusion

This technique is a powerful way to bulk-test public APIs in your .NET projects with minimal effort. By combining reflection, FluentAssertions, xUnit and dependency injection, you can get immediate feedback on method stability and surface hidden exceptions early.

Use it as a safety net alongside your unit tests and you’ll catch a surprising number of regressions or missed registrations!