SMock - Static & Instance Method Mocking for .NET
The only .NET library that makes testing static methods effortless!
Why SMock?
SMock revolutionizes .NET testing by breaking down the barriers that make legacy code, third-party dependencies, and static APIs difficult to test. Built on the powerful MonoMod runtime modification technology, SMock provides capabilities that other mocking frameworks simply cannot offer.
Key Features
- ๐ฏ Mock Static Methods: The only .NET library that seamlessly handles static method mocking
- ๐จ Dual API Design: Choose between Hierarchical (validation-focused) or Sequential (disposable) patterns
- โก Zero Configuration: Works instantly with any test framework (NUnit, xUnit, MSTest)
- ๐ Complete Feature Set: Full support for async/await, parameter matching, callbacks, exceptions
- ๐ Safe & Isolated: Each test runs in complete isolation with automatic cleanup
- โก High Performance: Minimal runtime overhead with optimized IL modification
Installation
Package Manager Console
Install-Package SMock
.NET CLI
dotnet add package SMock
PackageReference
<PackageReference Include="SMock" Version="*" />
Compatibility: SMock supports .NET Standard 2.0+ and .NET Framework 4.62-4.81, ensuring broad compatibility across the .NET ecosystem.
Quick Start Examples
Sequential API - Clean & Scoped
Perfect for straightforward mocking with automatic cleanup:
[Test]
public void TestFileOperations()
{
// Mock file existence check
using var existsMock = Mock.Setup(() => File.Exists("config.json"))
.Returns(true);
// Mock file content reading
using var readMock = Mock.Setup(() => File.ReadAllText("config.json"))
.Returns("{\"setting\": \"test\"}");
// Your code under test
var configService = new ConfigurationService();
var setting = configService.GetSetting("setting");
Assert.AreEqual("test", setting);
} // Mocks automatically cleaned up here
Hierarchical API - Validation During Execution
Perfect for inline validation and complex testing scenarios:
[Test]
public void TestDatabaseOperations()
{
var expectedQuery = "SELECT * FROM Users WHERE Active = 1";
Mock.Setup(context => DatabaseHelper.ExecuteQuery(context.It.IsAny<string>()), () =>
{
// This validation runs DURING the mock execution
var result = DatabaseHelper.ExecuteQuery(expectedQuery);
Assert.IsNotNull(result);
Assert.IsTrue(result.Count > 0);
}).Returns(new List<User> { new User { Name = "Test User" } });
// Test your service
var userService = new UserService();
var activeUsers = userService.GetActiveUsers();
Assert.AreEqual(1, activeUsers.Count);
Assert.AreEqual("Test User", activeUsers.First().Name);
}
Core Concepts
Runtime Hook Technology
SMock uses advanced runtime modification techniques to intercept method calls:
// 1. Setup Phase - Create hook for target method
var mock = Mock.Setup(() => DateTime.Now);
// 2. Configuration - Define behavior
mock.Returns(new DateTime(2024, 1, 1));
// 3. Execution - Your code calls the original method, but gets the mock
var testTime = DateTime.Now; // Returns mocked value: 2024-01-01
// 4. Cleanup - Hook automatically removed (Sequential) or managed (Hierarchical)
Key Benefits
- ๐ฏ Non-Invasive: No source code changes required
- ๐ Isolated: Each test runs independently
- โก Fast: Minimal performance impact
- ๐งน Auto-Cleanup: Hooks automatically removed after tests
Advanced Features
Parameter Matching with It
SMock provides powerful parameter matching capabilities:
// Match any argument
Mock.Setup(context => MyService.Process(context.It.IsAny<string>()))
.Returns("mocked");
// Match with conditions
Mock.Setup(context => MyService.Process(context.It.Is<string>(s => s.StartsWith("test_"))))
.Returns("conditional_mock");
// Match specific values with complex conditions
Mock.Setup(context => DataProcessor.Transform(context.It.Is<DataModel>(d =>
d.IsValid && d.Priority > 5)))
.Returns(new ProcessedData { Success = true });
Async Method Support
Full support for asynchronous operations:
// Mock async methods (Sequential)
using var mock = Mock.Setup(() => HttpClient.GetStringAsync("https://api.example.com"))
.Returns(Task.FromResult("{\"data\": \"test\"}"));
// Mock async methods (Hierarchical)
Mock.Setup(context => DatabaseService.GetUserAsync(context.It.IsAny<int>()), async () =>
{
var user = await DatabaseService.GetUserAsync(123);
Assert.IsNotNull(user);
}).Returns(Task.FromResult(new User { Id = 123, Name = "Test" }));
Exception Handling
Easy exception testing:
// Sequential approach
using var mock = Mock.Setup(() => FileHelper.ReadConfig("invalid.json"))
.Throws<FileNotFoundException>();
Assert.Throws<FileNotFoundException>(() =>
FileHelper.ReadConfig("invalid.json"));
// Hierarchical approach
Mock.Setup(() => ApiClient.CallEndpoint("/api/test"), () =>
{
Assert.Throws<HttpRequestException>(() =>
ApiClient.CallEndpoint("/api/test"));
}).Throws<HttpRequestException>();
Callback Execution
Execute custom logic during mock calls:
var callCount = 0;
Mock.Setup(context => Logger.LogMessage(context.It.IsAny<string>()), () =>
{
var result = Logger.LogMessage("test");
Assert.IsTrue(callCount > 0);
}).Callback<string>(message =>
{
callCount++;
Console.WriteLine($"Logged: {message}");
});
Best Practices
Test Organization
[TestFixture]
public class ServiceTests
{
[SetUp]
public void Setup()
{
// SMock works with any setup/teardown approach
// No special initialization required
}
[Test]
public void Should_Handle_FileSystem_Operations()
{
// Group related mocks together
using var existsMock = Mock.Setup(context => File.Exists(context.It.IsAny<string>()))
.Returns(true);
using var readMock = Mock.Setup(context => File.ReadAllText(context.It.IsAny<string>()))
.Returns("test content");
// Test your logic
var processor = new FileProcessor();
var result = processor.ProcessFiles();
Assert.IsNotNull(result);
}
}
Performance Considerations
- Mock Reuse: Create mocks once per test method
- Cleanup: Always use
usingstatements with Sequential API - Scope: Keep mock scope as narrow as possible
- Validation: Use Hierarchical API when you need immediate validation
Framework Support
SMock integrates seamlessly with all major .NET testing frameworks:
| Framework | Support | Notes |
|---|---|---|
| NUnit | โ Full | Recommended for attribute-based testing |
| xUnit | โ Full | Excellent for fact/theory patterns |
| MSTest | โ Full | Perfect for Visual Studio integration |
| Custom | โ Full | Works with any testing approach |
Troubleshooting
Common Issues
Mock Not Triggering
// โ Incorrect - Mock won't trigger
Mock.Setup(() => MyClass.Method()).Returns("test");
MyClass.Method(); // Different instance
// โ
Correct - Mock triggers properly
Mock.Setup(() => MyClass.Method()).Returns("test");
var result = MyClass.Method(); // Same static call
Parameter Matching Issues
// โ Incorrect - Too specific
Mock.Setup(() => MyClass.Process("exact_string")).Returns("result");
// โ
Better - Use parameter matching
Mock.Setup(context => MyClass.Process(context.It.IsAny<string>())).Returns("result");
Getting Help
- ๐ Full API Documentation
- ๐ Comprehensive Examples
- ๐ Report Issues
- ๐ฌ Join Discussions
What's Next?
Ready to dive deeper? Explore our comprehensive documentation:
๐ Getting Started
- Getting Started Guide - Detailed walkthrough with examples
- Testing Framework Integration - NUnit, xUnit, MSTest, and more
๐ Advanced Topics
- Advanced Usage Patterns - Complex scenarios and best practices
- Real-World Examples - Enterprise case studies and practical examples
- Performance Guide - Optimization strategies and benchmarks
๐ ๏ธ Reference & Support
- API Reference - Complete API documentation
- Migration Guide - Upgrading and switching from other frameworks
- Troubleshooting & FAQ - Solutions to common issues
Support the Project
SMock is developed and maintained as an open-source project. If you find it useful and would like to support its continued development, consider sponsoring the project:
- โญ GitHub Sponsors - Direct support through GitHub
- ๐ฏ Patreon - Monthly support with exclusive updates
- ๐ Boosty - Alternative sponsorship platform
Your support helps maintain the project, add new features, and provide community support. Every contribution, no matter the size, is greatly appreciated!
License
SMock is available under the MIT License.
Made with โค๏ธ by @SvetlovA and the SMock community