🎯 SMock - Static & Instance Method Mocking for .NET
Why SMock?
SMock breaks down the barriers of testing legacy code, third-party dependencies, and static APIs. Built on MonoMod runtime modification technology, SMock gives you the power to mock what others can't.
- Mock Static Methods: The only .NET library that handles static methods seamlessly
- Two API Styles: Choose Hierarchical (with validation) or Sequential (disposable) patterns
- Zero Configuration: Works with your existing test frameworks (NUnit, xUnit, MSTest)
- Complete Feature Set: Async/await, parameter matching, callbacks, exceptions, unsafe code
Installation
Package Manager
Install-Package SMock
.NET CLI
dotnet add package SMock
💡 Pro Tip: SMock works great with any testing framework - NUnit, xUnit, MSTest, you name it!
🚀 Quick Start
// Mock a static method in just one line - Sequential API
using var mock = Mock.Setup(() => File.ReadAllText("config.json"))
.Returns("{ \"setting\": \"test\" }");
// Your code now uses the mocked value!
var content = File.ReadAllText("config.json"); // Returns test JSON
// Or use the Hierarchical API with inline validation
Mock.Setup(() => DateTime.Now, () =>
{
var result = DateTime.Now;
Assert.AreEqual(new DateTime(2024, 1, 1), result);
}).Returns(new DateTime(2024, 1, 1));
Core Concepts
Hook-Based Runtime Modification
SMock uses MonoMod to create runtime hooks that intercept method calls:
- Non-Invasive: No source code changes required
- Isolated: Each test runs in isolation
- Fast: Minimal performance overhead
- Auto-Cleanup: Hooks automatically removed after test completion
Mock Lifecycle
// 1. Setup: Create a mock for the target method
var mock = Mock.Setup(() => DateTime.Now);
// 2. Configure: Define return values or behaviors
mock.Returns(new DateTime(2024, 1, 1));
// 3. Execute: Run your code - calls are intercepted
var now = DateTime.Now; // Returns mocked value
// 4. Cleanup: Dispose mock (Sequential) or automatic (Hierarchical)
mock.Dispose(); // Or automatic with 'using'
API Styles
SMock provides two distinct API patterns to fit different testing preferences:
Sequential API
Perfect for clean, scoped mocking with automatic cleanup:
[Test]
public void TestFileOperations()
{
// Mock file existence check
using var existsMock = Mock.Setup(() => File.Exists("test.txt"))
.Returns(true);
// Mock file content reading
using var readMock = Mock.Setup(() => File.ReadAllText("test.txt"))
.Returns("Hello World");
// Your code under test
var processor = new FileProcessor();
var result = processor.ProcessFile("test.txt");
Assert.AreEqual("HELLO WORLD", result);
} // Mocks automatically cleaned up here
Hierarchical API
Perfect for inline validation during mock execution:
[Test]
public void TestDatabaseConnection()
{
var expectedConnectionString = "Server=localhost;Database=test;";
Mock.Setup(context => DatabaseConnection.Connect(It.IsAny<string>()), () =>
{
// This validation runs DURING the mock execution
var actualCall = DatabaseConnection.Connect(expectedConnectionString);
Assert.IsNotNull(actualCall);
Assert.IsTrue(actualCall.IsConnected);
}).Returns(new MockConnection { IsConnected = true });
// Test your service
var service = new DatabaseService();
service.InitializeConnection(expectedConnectionString);
}
⚡ Performance
SMock is designed for minimal performance impact:
- Runtime Hooks: Only active during tests
- Zero Production Overhead: No dependencies in production builds
- Efficient Interception: Built on MonoMod's optimized IL modification
- Benchmarked: Comprehensive performance testing with BenchmarkDotNet
Performance Characteristics