Skip to content

Extends NSubstitute to support mocking concrete classes and static methods using Harmony runtime patching. Mock non-virtual methods, static methods, and sealed classes with familiar NSubstitute syntax.

License

Notifications You must be signed in to change notification settings

JayArrowz/NSubstituteConcrete

Repository files navigation

NSubstituteConcrete

A .NET library that extends NSubstitute to support mocking concrete classes and static methods using Harmony runtime patching.

Build Status NuGet License: MIT Buy Me A Coffee

Features

  • Concrete class mocking: Mock non-virtual methods in concrete classes
  • Static method mocking: Mock static methods with familiar NSubstitute syntax
  • Harmony-powered: Uses Harmony library for runtime IL patching
  • NSubstitute syntax: Familiar API for existing NSubstitute users
  • Zero configuration: Works with existing concrete classes without modification

Installation

dotnet add package NSubstituteConcrete

Usage

Concrete Class Mocking

using NSubstitute.Concrete;

public class UserService
{
    public string GetUserName(int id) => $"User{id}";
    public void SaveUser(User user) { /* implementation */ }
}

// Create substitute
var userService = NSubstituteExtensions.ForConcrete<UserService>();

// Setup methods
userService.Setup(x => x.GetUserName(1)).Returns("John Doe");
userService.Setup(x => x.SaveUser(It.IsAny<User>()))
           .Callback(() => Console.WriteLine("User saved"));

// Verify calls
userService.Verify(x => x.GetUserName(1), times: 1);

// Cleanup when done
userService.Cleanup();

Static Method Mocking

using NSubstitute.Concrete;

public static class FileHelper
{
    public static string ReadFile(string path) => File.ReadAllText(path);
    public static void DeleteFile(string path) => File.Delete(path);
}

// Setup static methods
Static.Setup(() => FileHelper.ReadFile("test.txt")).Returns("mock content");
Static.Setup(() => FileHelper.DeleteFile(It.IsAny<string>()))
      .Callback(() => Console.WriteLine("File deleted"));

// Use normally
string content = FileHelper.ReadFile("test.txt"); // Returns "mock content"

// Verify calls
Static.Verify(() => FileHelper.ReadFile("test.txt"), times: 1);

// Cleanup
Static.ClearAll();

Async Methods

var service = NSubstituteExtensions.ForConcrete<AsyncService>();

// Async methods with return values
service.SetupAsync(x => x.GetDataAsync(1)).Returns("result");

// Async void methods  
service.SetupAsync(x => x.ProcessAsync())
       .Callback(() => Console.WriteLine("Processing"));

// Static async methods
Static.SetupAsync(() => HttpClient.GetAsync("url")).Returns("response");

// Cleanup
service.Cleanup();

Properties

var service = NSubstituteExtensions.ForConcrete<ConfigService>();

// Setup property getters
service.SetupProperty(x => x.ConnectionString).Returns("test-connection");

// Set property values directly
service.SetProperty(x => x.Timeout, TimeSpan.FromSeconds(30));

// Cleanup
service.Cleanup();

Comparison with Other Testing Libraries

Feature NSubstituteConcrete Standard NSubstitute Moq TypeMock Isolator Microsoft Fakes
Concrete classes ✅ All methods ❌ Virtual only ❌ Virtual only ✅ All methods ✅ All methods
Static methods ✅ Full support ❌ Not supported ❌ Not supported ✅ Full support ✅ Full support
Non-virtual methods ✅ Supported ❌ Not supported ❌ Not supported ✅ Supported ✅ Supported
Sealed classes ✅ Supported ❌ Not supported ❌ Not supported ✅ Supported ✅ Supported
Cost 🆓 FREE 🆓 Free 🆓 Free 💰 ~$1,000+/license 💰 VS Enterprise required
Performance ⚡ Fast ⚡ Fast ⚡ Fast ⚠️ Slower (profiler) ⚠️ Slower (shims)
Setup complexity ✅ Simple ✅ Simple ✅ Simple ⚠️ Complex setup ⚠️ Complex setup
Syntax familiarity ✅ NSubstitute ✅ NSubstitute ❌ Different ❌ Different ❌ Different

Cleanup

Test Cleanup

public class MyTests : IDisposable
{
    public void Dispose()
    {
        // Clean up all substitutes and static patches
        ConcreteCleanupExtensions.ClearAll();
    }
}

Individual Cleanup

// Clean up specific substitute
substitute.Cleanup();

// Clean up only static method patches
Static.ClearAll();

Limitations

  • Method inlining: Aggressively optimized methods may be inlined and cannot be patched
  • Generic methods: Generic method patches may affect all type instantiations
  • Native methods: P/Invoke and external methods cannot be patched
  • Static constructors: May run at unexpected times during patching

Requirements

  • .NET 6.0 or later
  • Compatible with .NET Framework 4.8+ via .NET Standard 2.0

Dependencies

Building

git clone https://github.com/jayarrowz/NSubstituteConcrete.git
cd NSubstitute.Concrete
dotnet restore
dotnet build
dotnet test

Project Structure

NSubstitute.Concrete/
├── Core/                    # Core infrastructure
├── Static/                  # Static method support  
├── Setup/                   # Method setup classes
├── Utilities/               # Helper classes
└── Cleanup/                 # Cleanup and diagnostics

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please ensure tests pass and add tests for new functionality.

☕ Support

If you find NSubstituteConcrete useful and want to support its development:

Buy Me A Coffee

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Related Projects

  • NSubstitute - Original mocking library
  • Moq - Alternative mocking framework
  • Pose - Alternative method replacement library

About

Extends NSubstitute to support mocking concrete classes and static methods using Harmony runtime patching. Mock non-virtual methods, static methods, and sealed classes with familiar NSubstitute syntax.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages