geoder101.MoqProxy
1.0.6-alpha
See the version list below for details.
dotnet add package geoder101.MoqProxy --version 1.0.6-alpha
NuGet\Install-Package geoder101.MoqProxy -Version 1.0.6-alpha
<PackageReference Include="geoder101.MoqProxy" Version="1.0.6-alpha" />
<PackageVersion Include="geoder101.MoqProxy" Version="1.0.6-alpha" />
<PackageReference Include="geoder101.MoqProxy" />
paket add geoder101.MoqProxy --version 1.0.6-alpha
#r "nuget: geoder101.MoqProxy, 1.0.6-alpha"
#:package geoder101.MoqProxy@1.0.6-alpha
#addin nuget:?package=geoder101.MoqProxy&version=1.0.6-alpha&prerelease
#tool nuget:?package=geoder101.MoqProxy&version=1.0.6-alpha&prerelease
MoqProxy
A powerful extension for Moq that enables proxy pattern mocking - forward calls from a mock to a real implementation while maintaining full verification capabilities.
Why MoqProxy?
MoqProxy bridges the gap between full mocking and real implementations, giving you the best of both worlds:
- Verify interactions - Use Moq's
Verify()to assert method calls on the real implementation - Selective overrides - Override specific methods/properties while forwarding everything else
- Integration testing - Test decorators and wrappers with real dependencies
- Spy pattern - Observe and verify behavior without changing it
- Works with interfaces AND classes - Unlike
CallBase, works seamlessly with interface mocks
How is this different from CallBase = true?
| Feature | MoqProxy (SetupAsProxy) |
CallBase = true |
|---|---|---|
| Works with interfaces | ✅ Yes - forwards to any implementation | ❌ No - interfaces have no base implementation |
| Separate implementation | ✅ Forwards to a different instance | ❌ Only calls the mock's own base methods |
| Property synchronization | ✅ Mock and implementation stay in sync | ⚠️ Only if mock is the implementation |
| Use case | Spy on existing objects, test decorators | Partial mocking of concrete classes |
| Generic method support | ✅ Full support via custom interceptor | ✅ Supported |
| Indexer support | ✅ 1-2 parameter indexers | ✅ Supported |
Key Difference: CallBase = true only works with abstract or virtual members of the mocked class itself. SetupAsProxy works with interfaces and forwards calls to a separate implementation instance, making it perfect for the spy pattern and testing decorators.
Example Comparison
// ❌ This DOESN'T work - interface has no base implementation
var mock = new Mock<ICalculator> { CallBase = true };
mock.Object.Add(2, 3); // Throws - no implementation!
// ✅ This DOES work - forwards to real implementation
var realCalc = new Calculator();
var mock = new Mock<ICalculator>();
mock.SetupAsProxy(realCalc);
mock.Object.Add(2, 3); // Returns 5, calls realCalc.Add(2, 3)
Installation
dotnet add package geoder101.MoqProxy
Microsoft Dependency Injection Integration
For ASP.NET Core and Microsoft.Extensions.DependencyInjection scenarios, install the integration package:
dotnet add package geoder101.MoqProxy.DependencyInjection.Microsoft
This package allows you to wrap services registered in your DI container with Moq proxies, making it easy to verify calls and spy on real implementations in integration tests. See the package README for details.
Quick Start
using Moq;
using MoqProxy;
// Create a mock and a real implementation
var realService = new MyService();
var mock = new Mock<IMyService>();
// Set up the mock to proxy all calls to the real implementation
mock.SetupAsProxy(realService);
// Use the mock - calls are forwarded to realService
mock.Object.DoSomething();
// Verify the call was made
mock.Verify(m => m.DoSomething(), Times.Once);
Features
✅ Properties
- Read-only properties
- Write-only properties
- Read-write properties
- Complex type properties (collections, dictionaries, etc.)
- Null value handling
- State synchronization - changes to mock properties are reflected in the implementation and vice versa
✅ Methods
- Void methods
- Methods with return values
- Methods with 0-4+ parameters
- Method overloads
- Generic methods - full support including type inference
- Async methods -
TaskandTask<T> - Various return types (primitives, objects, collections, etc.)
✅ Indexers
- Single-parameter indexers (
this[int index]) - Multi-parameter indexers (
this[int x, int y]) - Read-only indexers
- Write-only indexers (limited support due to Moq constraints)
✅ Advanced Features
- Selective override - Override specific behaviors while keeping others proxied
- Mock reset - Call
mock.Reset()thenSetupAsProxy()again to restore proxying - Multiple instances - Proxy multiple implementations with different mocks
- Custom interceptor - Uses Castle.DynamicProxy for edge cases
Usage Examples
Basic Proxying
public interface ICalculator
{
int Add(int x, int y);
}
public class Calculator : ICalculator
{
public int Add(int x, int y) => x + y;
}
// Test
var impl = new Calculator();
var mock = new Mock<ICalculator>();
mock.SetupAsProxy(impl);
var result = mock.Object.Add(2, 3);
Assert.Equal(5, result);
mock.Verify(m => m.Add(2, 3), Times.Once);
Selective Override
var impl = new Calculator();
var mock = new Mock<ICalculator>();
mock.SetupAsProxy(impl);
// Override specific behavior
mock.Setup(m => m.Add(2, 3)).Returns(100);
// This call uses the override
Assert.Equal(100, mock.Object.Add(2, 3));
// Other calls are forwarded to the real implementation
Assert.Equal(7, mock.Object.Add(3, 4));
Testing Decorators
This is where MoqProxy really shines - testing decorator patterns:
public class CachingCalculatorDecorator : ICalculator
{
private readonly ICalculator _inner;
private readonly Dictionary<(int, int), int> _cache = new();
public CachingCalculatorDecorator(ICalculator inner)
{
_inner = inner;
}
public int Add(int x, int y)
{
if (_cache.TryGetValue((x, y), out var cached))
return cached;
var result = _inner.Add(x, y);
_cache[(x, y)] = result;
return result;
}
}
// Test
var impl = new Calculator();
var mock = new Mock<ICalculator>();
mock.SetupAsProxy(impl);
var decorator = new CachingCalculatorDecorator(mock.Object);
// First call - should call through
decorator.Add(2, 3);
mock.Verify(m => m.Add(2, 3), Times.Once);
// Second call - should be cached
decorator.Add(2, 3);
mock.Verify(m => m.Add(2, 3), Times.Once); // Still once - decorator cached it!
Async Methods
public interface IAsyncService
{
Task<string> GetDataAsync(int id);
Task ProcessAsync();
}
var impl = new AsyncService();
var mock = new Mock<IAsyncService>();
mock.SetupAsProxy(impl);
var result = await mock.Object.GetDataAsync(42);
await mock.Object.ProcessAsync();
mock.Verify(m => m.GetDataAsync(42), Times.Once);
mock.Verify(m => m.ProcessAsync(), Times.Once);
Properties with State Synchronization
public interface IConfig
{
string ConnectionString { get; set; }
}
var impl = new Config { ConnectionString = "Server=localhost" };
var mock = new Mock<IConfig>();
mock.SetupAsProxy(impl);
// Get property
Assert.Equal("Server=localhost", mock.Object.ConnectionString);
// Set property through mock
mock.Object.ConnectionString = "Server=production";
// Change is reflected in the implementation
Assert.Equal("Server=production", impl.ConnectionString);
// Both mock and impl are synchronized
Assert.Equal(impl.ConnectionString, mock.Object.ConnectionString);
Indexers
public interface IMatrix
{
int this[int x, int y] { get; set; }
}
var impl = new Matrix();
var mock = new Mock<IMatrix>();
mock.SetupAsProxy(impl);
// Set through indexer
mock.Object[0, 0] = 42;
// Get through indexer
var value = mock.Object[0, 0];
Assert.Equal(42, value);
Assert.Equal(42, impl[0, 0]); // Synchronized
Generic Methods
public interface IRepository
{
T GetById<T>(int id) where T : class;
void Save<T>(T entity) where T : class;
}
var impl = new Repository();
var mock = new Mock<IRepository>();
mock.SetupAsProxy(impl);
var user = mock.Object.GetById<User>(123);
mock.Object.Save(user);
mock.Verify(m => m.GetById<User>(123), Times.Once);
mock.Verify(m => m.Save(user), Times.Once);
Advanced Scenarios
Reset and Reapply
var impl = new Calculator();
var mock = new Mock<ICalculator>();
mock.SetupAsProxy(impl);
// Use the mock...
mock.Object.Add(2, 3);
// Override some behavior
mock.Setup(m => m.Add(It.IsAny<int>(), It.IsAny<int>())).Returns(999);
// Reset and reapply proxying
mock.Reset();
mock.SetupAsProxy(impl);
// Now back to forwarding to real implementation
Assert.Equal(5, mock.Object.Add(2, 3));
Limitations
- Ref/out parameters: Not supported due to Moq and expression tree limitations
- By-ref structs (e.g.,
Span<T>,ReadOnlySpan<T>): Not supported - Indexers with 3+ parameters: Limited support due to implementation complexity
- Write-only indexers: Have limited support due to Moq API constraints
How It Works
MoqProxy uses a sophisticated approach to enable proxy mocking:
- Reflection & Expression Trees: Dynamically inspects the mocked type and creates Moq setups using expression trees for properties, methods, and indexers
- Generic Method Handling: Uses
MethodInfo.Invokefor generic methods that can't be represented in expression trees - Custom Interceptor: Injects a Castle.DynamicProxy interceptor to handle edge cases and ensure all calls are forwarded
- Sentinel Pattern: Uses a special
NullReturnValuesentinel to detect when no explicit setup was matched, triggering fallback to the real implementation
The library handles complex scenarios including:
- Method overloads with different signatures
- Generic methods with type inference
- Multi-parameter indexers
- Async/await patterns
- Property state synchronization
Requirements
- .NET 6.0 or later
- Moq 4.20.72 or later
- Castle.Core (dependency of Moq)
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
License
This project is licensed under the MIT License - see the LICENSE.txt file for details.
Related Projects
- Moq - The mocking library this extends
- Castle.DynamicProxy - Used by Moq for proxy generation
Co-authored with Artificial Intelligence
This repository is part of an ongoing exploration into human-AI co-creation.
The code, comments, and structure emerged through dialogue between human intent and LLM reasoning — reviewed, refined, and grounded in human understanding.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. 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. |
-
net8.0
- Moq (>= 4.20.72)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on geoder101.MoqProxy:
| Package | Downloads |
|---|---|
|
geoder101.MoqProxy.DependencyInjection.Microsoft
Microsoft.Extensions.DependencyInjection integration for MoqProxy. Enables wrapping DI-registered services with Moq proxies for testing - verify calls to real implementations, spy on service interactions, and test integration scenarios without modifying production code. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.30 | 241 | 11/6/2025 |
| 1.0.30-alpha | 232 | 11/6/2025 |
| 1.0.25-alpha | 223 | 11/4/2025 |
| 1.0.22-alpha | 241 | 11/2/2025 |
| 1.0.12-alpha | 212 | 10/21/2025 |
| 1.0.6-alpha | 214 | 10/20/2025 |
| 1.0.1-alpha | 214 | 10/19/2025 |