SimplyMock 1.5.1

dotnet add package SimplyMock --version 1.5.1
                    
NuGet\Install-Package SimplyMock -Version 1.5.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SimplyMock" Version="1.5.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SimplyMock" Version="1.5.1" />
                    
Directory.Packages.props
<PackageReference Include="SimplyMock" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SimplyMock --version 1.5.1
                    
#r "nuget: SimplyMock, 1.5.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#addin nuget:?package=SimplyMock&version=1.5.1
                    
Install SimplyMock as a Cake Addin
#tool nuget:?package=SimplyMock&version=1.5.1
                    
Install SimplyMock as a Cake Tool

SimplyMock

A lightweight .NET method mocking library that allows you to dynamically replace methods and properties implementation in unit tests without creating full mock objects.

Features

  • Mock any method's return value with a simple API
  • Support for methods with various parameter counts (0-10 parameters)
  • Void method mocking support - Mock methods with no return value
  • Parameter mapping mode to return predefined results based on input parameters
  • Property getter mocking support
  • Mock exception-throwing behavior
  • Async method mocking support
  • Static method and static async method mocking
  • Generic method mocking support
  • Extension method mocking support - Mock extension methods including void ones
  • Automatic cleanup of all mocks (implements IDisposable)

Limitations

  • Each method can only be mocked once, subsequent mocks will throw an error
  • Methods with in, out, and ref parameter modifiers are not supported

Basic Usage

1. Create IMethodMocker instance

// Create an instance in your test class constructor
private readonly IMethodMocker _methodMocker;

public YourTestClass()
{
    _methodMocker = new MethodMocker();
}

// Release resources in the test class destructor
public void Dispose()
{
    _methodMocker.Dispose();
}

2. Mock method return values

// Mock a parameterless method
_methodMocker.MockMethodReturn<string>(
    typeof(YourClass),
    "GetMessage",
    () => "Mocked message"
);

// Mock a method with parameters
_methodMocker.MockMethodReturn<int, string>(
    typeof(YourClass),
    "GetById",
    (id) => $"Mocked item {id}"
);

// Mock a multi-parameter method (up to 10 parameters supported)
_methodMocker.MockMethodReturn<string, int, bool, string>(
    typeof(YourClass),
    "ProcessData",
    (name, code, isEnabled) => $"Processed: {name}"
);

// Example with more parameters (6 parameters)
_methodMocker.MockMethodReturn<string, int, bool, DateTime, double, char, string>(
    typeof(ComplexService),
    "ProcessComplexData",
    (name, count, flag, date, value, code) => $"Complex processing: {name}"
);

// Example with maximum parameters (10 parameters)
_methodMocker.MockMethodReturn<string, int, bool, DateTime, double, char, byte, long, float, decimal, string>(
    typeof(MaxParamService),
    "ProcessMaxParams",
    (p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) => $"Max params processed"
);

3. Use parameter mapping (supports up to 10 parameters)

// Single parameter mapping
var resultMap = new Dictionary<string, int>
{
    { "apple", 10 },
    { "banana", 20 },
    { "orange", 15 }
};

_methodMocker.MockMethodReturnMap<string, int>(
    typeof(FruitService),
    "GetPrice",
    resultMap
);

// Multi-parameter mapping with tuples (up to 10 parameters)
var multiParamMap = new Dictionary<(string, int, bool), string>
{
    { ("user1", 25, true), "Active user" },
    { ("user2", 30, false), "Inactive user" }
};

_methodMocker.MockMethodReturnMap<string, int, bool, string>(
    typeof(UserService),
    "GetUserStatus",
    multiParamMap
);

// Complex mapping with many parameters
var complexMap = new Dictionary<(string, int, bool, DateTime, double, char), string>
{
    { ("data", 100, true, DateTime.Now, 3.14, 'A'), "Complex result 1" },
    { ("info", 200, false, DateTime.Today, 2.71, 'B'), "Complex result 2" }
};

_methodMocker.MockMethodReturnMap<string, int, bool, DateTime, double, char, string>(
    typeof(ComplexService),
    "ProcessComplexMapping",
    complexMap
);

4. Mock properties

// Mock property getter
_methodMocker.MockPropertyGetter<bool>(
    typeof(User),
    "IsAdmin",
    () => true
);

5. Mock async methods

// Mock async method that returns Task<TResult>
_methodMocker.MockAsyncMethodReturn<string>(
    typeof(DataService),
    "GetDataAsync",  // public async Task<string> GetDataAsync()
    async () => {
        // Real async logic can be performed here
        await Task.Delay(10);
        return "Mocked async data";  // Returns string, method returns Task<string>
    }
);

// Mock async method with parameters that returns Task<TResult>
_methodMocker.MockAsyncMethodReturn<int, string>(
    typeof(OrderService),
    "ProcessOrderAsync",  // public async Task<string> ProcessOrderAsync(int orderId)
    async (orderId) => {
        await Task.Delay(5);
        return $"Processed order {orderId}";  // Returns string, method returns Task<string>
    }
);

// Mock async method that returns Task (no result - void async)
// Single parameter
_methodMocker.MockAsyncMethod<string>(
    typeof(AsyncService),
    "ProcessDataAsync",  // public async Task ProcessDataAsync(string data)
    async (data) => {
        // Simulate async operation
        await Task.Delay(10);
        Console.WriteLine($"Async processing: {data}");
        // No return value - method returns Task
    }
);

// Mock async Task method with two parameters
_methodMocker.MockAsyncMethod<string, int>(
    typeof(AsyncService),
    "ProcessDataWithCountAsync",  // public async Task ProcessDataWithCountAsync(string data, int count)
    async (data, count) => {
        await Task.Delay(10);
        Console.WriteLine($"Processing {data} with count {count}");
    }
);

// Mock async Task method with three parameters
_methodMocker.MockAsyncMethod<string, int, bool>(
    typeof(AsyncService),
    "ProcessUserAsync",  // public async Task ProcessUserAsync(string name, int age, bool isActive)
    async (name, age, isActive) => {
        await Task.Delay(10);
        Console.WriteLine($"Processing user: {name}, {age}, {isActive}");
    }
);

// Mock async Task method with four parameters
_methodMocker.MockAsyncMethod<string, int, bool, DateTime>(
    typeof(AsyncService),
    "ProcessComplexDataAsync",  // public async Task ProcessComplexDataAsync(string name, int count, bool flag, DateTime date)
    async (name, count, flag, date) => {
        await Task.Delay(10);
        Console.WriteLine($"Processing complex data: {name}, {count}, {flag}, {date}");
    }
);

// Mock async Task method with five parameters
_methodMocker.MockAsyncMethod<string, int, bool, DateTime, double>(
    typeof(AsyncService),
    "ProcessMaxParamsAsync",  // public async Task ProcessMaxParamsAsync(string name, int count, bool flag, DateTime date, double value)
    async (name, count, flag, date, value) => {
        await Task.Delay(10);
        Console.WriteLine($"Processing max params: {name}, {count}, {flag}, {date}, {value}");
    }
);

6. Mock void methods

// Mock void method with parameters
_methodMocker.MockVoidMethod<string, int>(
    typeof(DataProcessor),
    "ProcessData",
    (data, count) => {
        // Capture parameters and execute custom logic
        Console.WriteLine($"Processing {data} with count {count}");
    }
);

// Mock extension methods (like SetReferenceType)
_methodMocker.MockVoidMethod<ICommunicationInternalManager, string>(
    typeof(CommunicationExtensions),
    "SetReferenceType",
    (manager, referenceType) => {
        // Mock the extension method behavior
        Console.WriteLine($"SetReferenceType called with: {referenceType}");
    }
);

// Mock void method to throw exceptions
_methodMocker.MockVoidMethod<string>(
    typeof(FileService),
    "DeleteFile",
    (filePath) => {
        throw new FileNotFoundException($"File not found: {filePath}");
    }
);

// Mock async Task methods (void async methods)
_methodMocker.MockAsyncMethod<string>(
    typeof(AsyncService),
    "ProcessDataAsync",
    async (data) => {
        // Simulate async operation
        await Task.Delay(10);
        Console.WriteLine($"Async processing: {data}");
        // Can also throw exceptions or perform other logic
    }
);

7. Mock extension methods

// Define an extension method
public static class EnumExtensions
{
    public static string GetEnumDisplayName(this Enum value)
    {
        return value.ToString();
    }
}

public enum TestStatus
{
    Active,
    Inactive,
    Pending,
    Completed
}

// Mock the extension method
_methodMocker.MockMethodReturn<Enum, string>(
    typeof(EnumExtensions),
    "GetEnumDisplayName",
    (enumValue) => $"Mocked-{enumValue}"
);

// Use the mocked extension method
var result = TestStatus.Active.GetEnumDisplayName();
// result = "Mocked-Active"

// Extension method parameter mapping
var enumDisplayMap = new Dictionary<Enum, string>
{
    { TestStatus.Active, "活跃" },
    { TestStatus.Inactive, "非活跃" },
    { TestStatus.Pending, "待处理" },
    { TestStatus.Completed, "已完成" }
};

_methodMocker.MockMethodReturnMap<Enum, string>(
    typeof(EnumExtensions),
    "GetEnumDisplayName",
    enumDisplayMap
);

// Test different enum values
Assert.Equal("活跃", TestStatus.Active.GetEnumDisplayName());
Assert.Equal("非活跃", TestStatus.Inactive.GetEnumDisplayName());

// Extension method with complex logic
_methodMocker.MockMethodReturn<Enum, string>(
    typeof(EnumExtensions),
    "GetEnumDisplayName",
    (enumValue) =>
    {
        return enumValue switch
        {
            TestStatus.Active => "🟢 Active",
            TestStatus.Inactive => "🔴 Inactive", 
            TestStatus.Pending => "🟡 Pending",
            TestStatus.Completed => "✅ Completed",
            _ => "❓ Unknown"
        };
    }
);

8. Mock generic methods

// Mock a generic static method with no parameters
// Generic method: public static T Get<T>()
_methodMocker.MockGenericStaticMethodReturn<Person>(
    typeof(ServiceFactory),
    "Get",
    () => new Person { 
        Name = "John Doe", 
        Age = 30 
    }
);

// Mock a generic static method with one parameter
// Generic method: public static T Map<T>(object source)
_methodMocker.MockGenericStaticMethodReturn<UserDto, object>(
    typeof(Mapper),
    "Map",
    src => new UserDto {
        Id = 1001,
        Username = "johndoe",
        Email = "john@example.com"
    }
);

// Using the mocked generic methods
Person person = ServiceFactory.Get<Person>();
UserDto userDto = Mapper.Map<UserDto>(sourceObject);

8. Dynamically control static generic method return values with an external variable

Sometimes you may want to flexibly change the return value of a static generic method during your test. You can achieve this by combining an external static variable with a mock:

// Define a static class to control the return value externally
// The MockReturn<T> class now automatically uses the type's full name as the key
// This allows different types to have separate instances without manual naming
public static class MockReturn<T>
{
    private static readonly Dictionary<string, T> _instances = new();
    private static readonly object _lock = new();

    /// <summary>
    /// Gets the automatic name based on the type's full name
    /// </summary>
    private static string GetTypeName()
    {
        return typeof(T).FullName ?? typeof(T).Name;
    }

    public static T GetInstance(string name)
    {
        lock (_lock)
        {
            _instances.TryGetValue(name, out var instance);
            return instance;
        }
    }

    public static void SetInstance(string name, T instance)
    {
        lock (_lock)
        {
            _instances[name] = instance;
        }
    }

    /// <summary>
    /// Gets instance using the type's full name as the key
    /// This automatically separates different types without manual naming
    /// </summary>
    public static T GetInstance()
    {
        return GetInstance(GetTypeName());
    }

    /// <summary>
    /// Sets instance using the type's full name as the key
    /// This automatically separates different types without manual naming
    /// </summary>
    public static void SetInstance(T instance)
    {
        SetInstance(GetTypeName(), instance);
    }

    public static IEnumerable<string> GetAllNames()
    {
        lock (_lock)
        {
            return _instances.Keys.ToList();
        }
    }
}

// Example static generic method
public static class StaticProvider
{
    public static T Get<T>() => default;
}

public class StaticMethodMock_ExternalVariable_Tests : IDisposable
{
    private readonly IMethodMocker _methodMocker;

    public StaticMethodMock_ExternalVariable_Tests()
    {
        _methodMocker = new MethodMocker();
    }

    public void Dispose()
    {
        _methodMocker.Dispose();
    }

    [Fact]
    public void Mock_StaticGenericMethod_Returns_ExternalVariable()
    {
        // Arrange - Now you can set instances for different types automatically
        var expectedString = "Hello, Mock!";
        var expectedInt = 42;
        
        // Each type automatically gets its own instance based on type name
        MockReturn<string>.SetInstance(expectedString);
        MockReturn<int>.SetInstance(expectedInt);

        // Mock the static generic method to return the external variable
        _methodMocker.MockGenericStaticMethodReturn<string>(
            typeof(StaticProvider),
            "Get",
            () => MockReturn<string>.GetInstance()
        );
        
        _methodMocker.MockGenericStaticMethodReturn<int>(
            typeof(StaticProvider),
            "Get",
            () => MockReturn<int>.GetInstance()
        );

        // Act & Assert - Different types return their respective values
        Assert.Equal("Hello, Mock!", StaticProvider.Get<string>());
        Assert.Equal(42, StaticProvider.Get<int>());

        // Change the external variable and call again
        MockReturn<string>.SetInstance("Changed!");
        MockReturn<int>.SetInstance(100);
        
        Assert.Equal("Changed!", StaticProvider.Get<string>());
        Assert.Equal(100, StaticProvider.Get<int>());
    }
    
    [Fact]
    public void Mock_StaticGenericMethod_SupportsNamedInstances()
    {
        // You can still use named instances for more complex scenarios
        MockReturn<string>.SetInstance("scenario1", "First scenario");
        MockReturn<string>.SetInstance("scenario2", "Second scenario");
        
        // Mock different behaviors based on named instances
        _methodMocker.MockGenericStaticMethodReturn<string>(
            typeof(StaticProvider),
            "Get",
            () => MockReturn<string>.GetInstance("scenario1")
        );
        
        Assert.Equal("First scenario", StaticProvider.Get<string>());
        
        // Switch to different scenario
        _methodMocker.MockGenericStaticMethodReturn<string>(
            typeof(StaticProvider),
            "Get",
            () => MockReturn<string>.GetInstance("scenario2")
        );
        
        Assert.Equal("Second scenario", StaticProvider.Get<string>());
    }
}

Advanced Usage

Mock exceptions

// Mock method to throw exceptions
_methodMocker.MockMethodReturn<string, string>(
    typeof(UserService),
    "GetUserName",
    (userId) => {
        if (string.IsNullOrEmpty(userId))
            throw new ArgumentNullException(nameof(userId));
            
        if (userId == "invalid")
            throw new InvalidOperationException("User not found");
            
        return $"User {userId}";
    }
);

Notes

  1. Ensure you call Dispose() after tests to release all mocks
  2. When mocking methods, parameter types and count must match exactly
  3. When using MockMethodReturnMap, undefined parameter combinations will throw KeyNotFoundException
  4. Async method mocking supports real async operations, you can use await in replacement functions
  5. SimplyMock supports methods with up to 5 parameters, for both sync and async methods
  6. Static methods and static async methods are mocked the same way as instance methods
  7. Generic static methods can be mocked by providing a Func delegate directly
  8. Multiple mocks on the same method will override previous implementations
  9. SimplyMock modifies method behavior at runtime, making it ideal for testing legacy code or third-party libraries
  10. Void method mocking allows you to mock methods with no return value, including extension methods
  11. Extension method mocking: For extension methods, the first parameter in the mock should be the this parameter type
  12. Void method parameter capture: Use Action delegates to capture method parameters and execute custom logic
  13. Async Task method mocking: Use MockAsyncMethod to mock methods that return Task (void async methods) - these support real async operations with await

Complete Example

using System;
using Xunit;
using SimplyMock;

public class CalculatorService
{
    public int Add(int a, int b) => a + b;
}

public class CalculatorTests : IDisposable
{
    private readonly IMethodMocker _methodMocker;

    public CalculatorTests()
    {
        _methodMocker = new MethodMocker();
    }

    public void Dispose()
    {
        _methodMocker.Dispose();
    }

    [Fact]
    public void Add_ShouldReturnMockedResult()
    {
        // Replace the implementation of Add method
        _methodMocker.MockMethodReturn<int, int, int>(
            typeof(CalculatorService),
            "Add",
            (a, b) => a * b // Replace with multiplication
        );

        var calculator = new CalculatorService();
        int result = calculator.Add(5, 3);

        // Now Add method performs multiplication
        Assert.Equal(15, result);
    }
}

Void Method Complete Example

using System;
using Xunit;
using SimplyMock;

public interface ICommunicationManager
{
    // Interface definition
}

public class TestCommunicationManager : ICommunicationManager
{
    // Implementation
}

public static class CommunicationExtensions
{
    public static void SetReferenceType(this ICommunicationManager manager, string referenceType)
    {
        // Original extension method implementation
        Console.WriteLine($"Original SetReferenceType: {referenceType}");
    }
}

public class VoidMethodTests : IDisposable
{
    private readonly IMethodMocker _methodMocker;

    public VoidMethodTests()
    {
        _methodMocker = new MethodMocker();
    }

    public void Dispose()
    {
        _methodMocker.Dispose();
    }

    [Fact]
    public void SetReferenceType_ShouldBeMocked()
    {
        // Arrange - Mock the extension method
        string capturedReferenceType = null;
        ICommunicationManager capturedManager = null;
        
        _methodMocker.MockVoidMethod<ICommunicationManager, string>(
            typeof(CommunicationExtensions),
            nameof(CommunicationExtensions.SetReferenceType),
            (manager, referenceType) =>
            {
                capturedManager = manager;
                capturedReferenceType = referenceType;
                Console.WriteLine($"Mocked SetReferenceType: {referenceType}");
            });

        var manager = new TestCommunicationManager();

        // Act - Call the mocked extension method
        manager.SetReferenceType("TestReference");

        // Assert - Verify the mock was called
        Assert.Same(manager, capturedManager);
        Assert.Equal("TestReference", capturedReferenceType);
    }
}
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.5.1 133 6/3/2025
1.5.0 140 5/29/2025
1.4.0 137 5/28/2025
1.3.3 139 5/27/2025
1.3.2 134 5/20/2025
1.3.1 133 5/20/2025
1.3.0 138 5/20/2025
1.2.0 189 5/16/2025
1.1.0 194 5/16/2025
1.0.0 211 5/15/2025

Version 1.5.1:
     change Lib.Harmony nuget Version to 2.3.6:
     Version 1.5.0:
     - Extended parameter support: All mock methods now support up to 10 parameters (previously 5)
     - Added MockMethodReturn, MockMethodReturnMap, MockVoidMethod, MockAsyncMethodReturn, and MockAsyncMethod overloads for 6-10 parameters
     - Modular architecture with partial classes for better maintainability
     - 100% backward compatible - no breaking changes
     - Comprehensive test coverage with 27 new test cases