Troubleshooting Guide & FAQ
This comprehensive guide covers common issues, solutions, and frequently asked questions about SMock.
Table of Contents
- Quick Diagnostics
- Common Issues
- Mock Setup Problems
- Runtime Issues
- Performance Problems
- Integration Issues
- Frequently Asked Questions
- Getting Help
Quick Diagnostics
SMock Health Check
Run this quick test to verify SMock is working correctly:
[Test]
public void SMock_Health_Check()
{
try
{
// Test basic static method mocking
using var mock = Mock.Setup(() => DateTime.Now)
.Returns(new DateTime(2024, 1, 1));
var result = DateTime.Now;
Assert.AreEqual(new DateTime(2024, 1, 1), result);
Console.WriteLine("✅ SMock is working correctly!");
}
catch (Exception ex)
{
Console.WriteLine($"❌ SMock issue detected: {ex.Message}");
throw;
}
}
Environment Verification
[Test]
public void Verify_Environment()
{
Console.WriteLine($"Runtime: {RuntimeInformation.FrameworkDescription}");
Console.WriteLine($"OS: {RuntimeInformation.OSDescription}");
Console.WriteLine($"Architecture: {RuntimeInformation.ProcessArchitecture}");
var smockAssembly = Assembly.GetAssembly(typeof(Mock));
Console.WriteLine($"SMock Version: {smockAssembly.GetName().Version}");
// Check for MonoMod assemblies
try
{
var monoModAssembly = Assembly.LoadFrom("MonoMod.Core.dll");
Console.WriteLine($"MonoMod.Core: {monoModAssembly.GetName().Version}");
}
catch (Exception ex)
{
Console.WriteLine($"⚠️ MonoMod.Core not found: {ex.Message}");
}
}
Common Issues
Issue 1: Compiler Optimization Preventing Mock Application
Symptoms: Mock setup appears correct, but original method is still called, especially in Release builds.
Root Cause: Compiler optimizations can inline or optimize method calls, preventing SMock's runtime hooks from intercepting them.
Diagnostic Steps:
[Test]
public void Debug_Optimization_Issue()
{
Console.WriteLine($"Current Configuration: {GetBuildConfiguration()}");
using var mock = Mock.Setup(() => DateTime.Now)
.Returns(new DateTime(2024, 1, 1));
var result = DateTime.Now;
Console.WriteLine($"Mocked result: {result}");
Console.WriteLine($"Expected: {new DateTime(2024, 1, 1)}");
if (result != new DateTime(2024, 1, 1))
{
Console.WriteLine("⚠️ Mock not applied - likely due to compiler optimization");
}
}
private string GetBuildConfiguration()
{
#if DEBUG
return "Debug";
#else
return "Release";
#endif
}
Solutions:
Run tests in Debug configuration:
dotnet test --configuration DebugDisable compiler optimization in your test project by adding this to your
.csproj:<PropertyGroup Condition="'$(Configuration)' == 'Release'"> <Optimize>false</Optimize> </PropertyGroup>Disable optimization for specific methods using the
MethodImplattribute:using System.Runtime.CompilerServices; [MethodImpl(MethodImplOptions.NoOptimization)] [Test] public void MyTestMethod() { using var mock = Mock.Setup(() => File.ReadAllText("config.json")) .Returns("{ \"setting\": \"test\" }"); var result = File.ReadAllText("config.json"); Assert.AreEqual("{ \"setting\": \"test\" }", result); }
Best Practice: Always test your mocking setup in both Debug and Release configurations to catch optimization issues early.
Issue 2: Mock Not Triggering (Parameter/Signature Issues)
Symptoms: Mock setup appears correct, but original method is still called due to parameter or signature mismatches.
Diagnostic Steps:
[Test]
public void Debug_Mock_Not_Triggering()
{
// Step 1: Verify exact method signature
using var mock = Mock.Setup(() => File.ReadAllText("test.txt"))
.Returns("mocked content");
// Step 2: Test with exact parameters
var result1 = File.ReadAllText("test.txt");
Console.WriteLine($"Exact match result: {result1}");
// Step 3: Test with different parameters (should call original)
try
{
var result2 = File.ReadAllText("different.txt");
Console.WriteLine($"Different parameter result: {result2}");
}
catch (FileNotFoundException)
{
Console.WriteLine("Different parameter called original method (expected)");
}
}
Common Causes & Solutions:
Parameter Mismatch:
// ❌ Problem: Too specific Mock.Setup(() => MyClass.Method("exact_value")).Returns("result"); // ✅ Solution: Use parameter matching Mock.Setup(context => MyClass.Method(context.It.IsAny<string>())).Returns("result");Method Overload Confusion:
// ❌ Problem: Wrong overload Mock.Setup(context => Convert.ToString(context.It.IsAny<object>())).Returns("mocked"); // ✅ Solution: Specify exact overload Mock.Setup(context => Convert.ToString(context.It.IsAny<int>())).Returns("mocked");Generic Method Issues:
// ❌ Problem: Generic type not resolved Mock.Setup(context => JsonSerializer.Deserialize<object>(context.It.IsAny<string>())) .Returns(new { test = "value" }); // ✅ Solution: Specify concrete type Mock.Setup(context => JsonSerializer.Deserialize<MyClass>(context.It.IsAny<string>())) .Returns(new MyClass { Test = "value" });
Issue 3: Assembly Loading Failures
Symptoms: FileNotFoundException, BadImageFormatException, or similar assembly errors.
Diagnostic Code:
[Test]
public void Debug_Assembly_Loading()
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(a => a.GetName().Name.Contains("MonoMod") ||
a.GetName().Name.Contains("SMock"))
.ToList();
foreach (var assembly in loadedAssemblies)
{
Console.WriteLine($"Loaded: {assembly.GetName().Name} v{assembly.GetName().Version}");
Console.WriteLine($"Location: {assembly.Location}");
}
if (!loadedAssemblies.Any())
{
Console.WriteLine("⚠️ No SMock/MonoMod assemblies found!");
}
}
Solutions:
Clean and Restore:
dotnet clean rm -rf bin obj # or del bin obj /s /q on Windows dotnet restore dotnet buildCheck Package References:
<!-- Ensure these are in your .csproj --> <PackageReference Include="SMock" Version="[latest-version]" /> <!-- If issues persist, add explicit MonoMod references --> <PackageReference Include="MonoMod.Core" Version="[compatible-version]" />Runtime Configuration:
<!-- Add to your test project's app.config or test.runsettings --> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="bin;lib" /> </assemblyBinding> </runtime>
Issue 4: Performance Degradation
Symptoms: Tests run significantly slower after adding SMock.
Performance Profiling:
[Test]
public void Profile_Mock_Performance()
{
var stopwatch = Stopwatch.StartNew();
// Measure mock setup time
var setupStart = stopwatch.ElapsedMilliseconds;
using var mock = Mock.Setup(() => DateTime.Now).Returns(new DateTime(2024, 1, 1));
var setupTime = stopwatch.ElapsedMilliseconds - setupStart;
// Measure mock execution time
var executionStart = stopwatch.ElapsedMilliseconds;
for (int i = 0; i < 1000; i++)
{
var _ = DateTime.Now;
}
var executionTime = stopwatch.ElapsedMilliseconds - executionStart;
Console.WriteLine($"Setup time: {setupTime}ms");
Console.WriteLine($"Execution time (1000 calls): {executionTime}ms");
Console.WriteLine($"Per-call overhead: {(double)executionTime / 1000:F3}ms");
// Acceptable thresholds
Assert.Less(setupTime, 10, "Setup should be under 10ms");
Assert.Less((double)executionTime / 1000, 0.1, "Per-call overhead should be under 0.1ms");
}
Optimization Strategies:
Reduce Mock Scope:
// ❌ Avoid: Creating unnecessary mocks [Test] public void Wasteful_Mocking() { using var mock1 = Mock.Setup(() => Method1()).Returns("value1"); using var mock2 = Mock.Setup(() => Method2()).Returns("value2"); // Not used! using var mock3 = Mock.Setup(() => Method3()).Returns("value3"); // Only Method1 is actually called in test Assert.AreEqual("value1", Method1()); } // ✅ Better: Only mock what you need [Test] public void Efficient_Mocking() { using var mock = Mock.Setup(() => Method1()).Returns("value1"); Assert.AreEqual("value1", Method1()); }Use Lazy Initialization:
[Test] public void Lazy_Mock_Initialization() { Lazy<IDisposable> expensiveMock = new(() => Mock.Setup(() => ExpensiveExternalService.Call()) .Returns("cached_result")); var service = new TestService(); // Mock only created if needed if (service.NeedsExternalCall()) { using var mock = expensiveMock.Value; service.DoWork(); } }
Mock Setup Problems
Parameter Matching Issues
Problem: Parameter matchers not working as expected.
[Test]
public void Debug_Parameter_Matching()
{
// Test different parameter matching strategies
var callLog = new List<string>();
using var mock = Mock.Setup(context => TestClass.ProcessData(context.It.IsAny<string>()))
.Callback<string>(data => callLog.Add($"Called with: {data}"))
.Returns("mocked");
// These should all trigger the mock
TestClass.ProcessData("test1");
TestClass.ProcessData("test2");
TestClass.ProcessData(null);
Console.WriteLine("Calls captured:");
callLog.ForEach(Console.WriteLine);
Assert.AreEqual(3, callLog.Count, "All calls should be captured");
}
Advanced Parameter Matching:
[Test]
public void Advanced_Parameter_Matching()
{
// Complex object matching
using var mock = Mock.Setup(context =>
DataProcessor.Process(context.It.Is<ProcessRequest>(req =>
req.Priority > 5 &&
req.Type == "Important" &&
req.Data.Contains("test"))))
.Returns(new ProcessResult { Success = true });
var request = new ProcessRequest
{
Priority = 10,
Type = "Important",
Data = "test_data_here"
};
var result = DataProcessor.Process(request);
Assert.IsTrue(result.Success);
}
Async Method Mocking Issues
Problem: Async methods not mocking correctly.
[Test]
public async Task Debug_Async_Mocking()
{
// ❌ Common mistake: Wrong return type
// Mock.Setup(() => AsyncService.GetDataAsync()).Returns("data"); // Won't compile
// ✅ Correct approaches:
// Option 1: Task.FromResult
using var mock1 = Mock.Setup(() => AsyncService.GetDataAsync())
.Returns(Task.FromResult("mocked_data"));
var result1 = await AsyncService.GetDataAsync();
Assert.AreEqual("mocked_data", result1);
// Option 2: Async lambda
using var mock2 = Mock.Setup(context => AsyncService.ProcessAsync(context.It.IsAny<string>()))
.Returns(async (string input) =>
{
await Task.Delay(1); // Simulate async work
return $"processed_{input}";
});
var result2 = await AsyncService.ProcessAsync("test");
Assert.AreEqual("processed_test", result2);
}
Runtime Issues
Hook Conflicts
Problem: Multiple mocks interfering with each other.
[Test]
public void Debug_Hook_Conflicts()
{
var calls = new List<string>();
// Create multiple mocks for the same method
using var mock1 = Mock.Setup(context => Logger.Log(context.It.IsAny<string>()))
.Callback<string>(msg => calls.Add($"Mock1: {msg}"))
.Returns();
// This might conflict with mock1
using var mock2 = Mock.Setup(context => Logger.Log(context.It.IsAny<string>()))
.Callback<string>(msg => calls.Add($"Mock2: {msg}"))
.Returns();
Logger.Log("test_message");
Console.WriteLine("Captured calls:");
calls.ForEach(Console.WriteLine);
// Only the last mock should be active
Assert.AreEqual(1, calls.Count);
Assert.IsTrue(calls[0].Contains("Mock2"));
}
Solution: Use single mock with conditional logic:
[Test]
public void Resolved_Conditional_Mocking()
{
var calls = new List<string>();
using var mock = Mock.Setup(context => Logger.Log(context.It.IsAny<string>()))
.Callback<string>(msg =>
{
if (msg.StartsWith("error"))
calls.Add($"Error logged: {msg}");
else
calls.Add($"Info logged: {msg}");
})
.Returns();
Logger.Log("error: Something went wrong");
Logger.Log("info: Everything is fine");
Assert.AreEqual(2, calls.Count);
Assert.IsTrue(calls[0].Contains("Error logged"));
Assert.IsTrue(calls[1].Contains("Info logged"));
}
Memory Leaks
Problem: Memory usage grows over time during test execution.
Diagnostic Tool:
[Test]
public void Monitor_Memory_Usage()
{
var initialMemory = GC.GetTotalMemory(true);
for (int i = 0; i < 100; i++)
{
using var mock = Mock.Setup(() => DateTime.Now)
.Returns(new DateTime(2024, 1, 1));
var _ = DateTime.Now;
}
var finalMemory = GC.GetTotalMemory(true);
var memoryIncrease = finalMemory - initialMemory;
Console.WriteLine($"Initial memory: {initialMemory:N0} bytes");
Console.WriteLine($"Final memory: {finalMemory:N0} bytes");
Console.WriteLine($"Memory increase: {memoryIncrease:N0} bytes");
// Memory increase should be minimal
Assert.Less(memoryIncrease, 1_000_000, "Memory increase should be under 1MB");
}
Prevention: Always dispose mocks properly:
[Test]
public void Proper_Mock_Disposal()
{
// ✅ Good: Using statement ensures disposal
using var mock = Mock.Setup(context => File.Exists(context.It.IsAny<string>()))
.Returns(true);
// Test logic here
// ✅ Good: Explicit disposal if using statement not possible
var mock2 = Mock.Setup(context => File.ReadAllText(context.It.IsAny<string>()))
.Returns("content");
try
{
// Test logic
}
finally
{
mock2?.Dispose();
}
}
Performance Problems
Slow Test Execution
Performance Analysis:
For comprehensive performance analysis, use the official benchmark project:
# Run the official benchmarks
cd src
dotnet run --project StaticMock.Tests.Benchmark --configuration Release
For custom performance testing in your own projects:
[Test]
public void Profile_Mock_Performance()
{
var stopwatch = Stopwatch.StartNew();
// Measure setup time
var setupStart = stopwatch.ElapsedMilliseconds;
using var mock = Mock.Setup(() => DateTime.Now).Returns(new DateTime(2024, 1, 1));
var setupTime = stopwatch.ElapsedMilliseconds - setupStart;
// Measure execution time
var executionStart = stopwatch.ElapsedMilliseconds;
for (int i = 0; i < 1000; i++)
{
var _ = DateTime.Now;
}
var executionTime = stopwatch.ElapsedMilliseconds - executionStart;
Console.WriteLine($"Setup time: {setupTime}ms");
Console.WriteLine($"Execution time (1000 calls): {executionTime}ms");
Console.WriteLine($"Per-call overhead: {(double)executionTime / 1000:F3}ms");
// Acceptable thresholds
Assert.Less(setupTime, 10, "Setup should be under 10ms");
Assert.Less((double)executionTime / 1000, 0.1, "Per-call overhead should be under 0.1ms");
}
Integration Issues
Test Framework Compatibility
NUnit Integration Issues:
[TestFixture]
public class NUnitIntegrationTests
{
[Test]
public void SMock_Works_With_NUnit()
{
using var mock = Mock.Setup(() => Environment.MachineName)
.Returns("TEST_MACHINE");
Assert.AreEqual("TEST_MACHINE", Environment.MachineName);
}
}
xUnit Integration:
public class XUnitIntegrationTests : IDisposable
{
private readonly List<IDisposable> _mocks = new List<IDisposable>();
[Fact]
public void SMock_Works_With_xUnit()
{
var mock = Mock.Setup(() => DateTime.UtcNow)
.Returns(new DateTime(2024, 1, 1));
_mocks.Add(mock);
Assert.Equal(new DateTime(2024, 1, 1), DateTime.UtcNow);
}
public void Dispose()
{
_mocks.ForEach(m => m?.Dispose());
_mocks.Clear();
}
}
CI/CD Pipeline Issues
Problem: Tests pass locally but fail in CI/CD.
Diagnostic Script (for CI):
[Test]
public void CI_Environment_Check()
{
Console.WriteLine("=== CI/CD Environment Diagnostics ===");
Console.WriteLine($"OS: {Environment.OSVersion}");
Console.WriteLine($"Runtime: {RuntimeInformation.FrameworkDescription}");
Console.WriteLine($"Architecture: {RuntimeInformation.ProcessArchitecture}");
Console.WriteLine($"Is64BitProcess: {Environment.Is64BitProcess}");
Console.WriteLine($"WorkingDirectory: {Directory.GetCurrentDirectory()}");
// Check for restricted environments
try
{
using var mock = Mock.Setup(() => DateTime.Now)
.Returns(new DateTime(2024, 1, 1));
Console.WriteLine("✅ Basic mocking works");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Basic mocking failed: {ex.Message}");
Console.WriteLine($"Stack trace: {ex.StackTrace}");
throw;
}
}
Frequently Asked Questions
Q: Can SMock mock sealed classes?
A: SMock mocks methods, not classes. It can mock static methods on sealed classes, but cannot mock instance methods on sealed classes that aren't virtual.
// ✅ This works - static method on sealed class
using var mock = Mock.Setup(context => File.ReadAllText(context.It.IsAny<string>()))
.Returns("mocked content");
// ❌ This won't work - instance method on sealed class
// var sealedInstance = new SealedClass();
// Mock.Setup(() => sealedInstance.NonVirtualMethod()).Returns("value");
Q: How does SMock compare performance-wise to other mocking frameworks?
A: SMock has minimal runtime overhead for method interception (<0.1ms per call), but higher setup cost (~1-2ms) due to IL modification. For most testing scenarios, this is negligible.
Q: Can I use SMock in production code?
A: No, SMock is designed exclusively for testing. It modifies runtime behavior and should never be used in production builds.
Q: Does SMock work with .NET Native/AOT?
A: SMock requires runtime IL modification capabilities that may not be available in AOT-compiled applications. It's designed for traditional .NET runtimes.
Q: Can I mock methods from third-party libraries?
A: Yes, SMock can mock any static method from any assembly, including third-party libraries.
// Works with third-party libraries
using var mock = Mock.Setup(context => JsonConvert.SerializeObject(context.It.IsAny<object>()))
.Returns("{\"mocked\": true}");
Q: How do I verify that a mocked method was called?
A: Use callbacks to track method calls:
[Test]
public void Verify_Method_Called()
{
var wasCalled = false;
using var mock = Mock.Setup(context => Logger.Log(context.It.IsAny<string>()))
.Callback<string>(msg => wasCalled = true);
// Your code that should call Logger.Log
MyService.DoSomething();
Assert.IsTrue(wasCalled, "Logger.Log should have been called");
}
Q: Can I mock generic methods?
A: Yes, but you need to specify the generic type parameters:
// ✅ Specify the generic type
using var mock = Mock.Setup(context => JsonSerializer.Deserialize<MyClass>(context.It.IsAny<string>()))
.Returns(new MyClass());
// ❌ Don't use open generic types
// Mock.Setup(context => JsonSerializer.Deserialize<T>(context.It.IsAny<string>()))
Getting Help
Self-Diagnosis Checklist
Before seeking help, run through this checklist:
- [ ] SMock package is properly installed and up-to-date
- [ ] Mock setup syntax is correct (parameter matching, return types)
- [ ] Using statements or proper disposal for Sequential API
- [ ] No conflicting mocks for the same method
- [ ] Test framework compatibility verified
- [ ] Environment supports runtime IL modification
Reporting Issues
When reporting issues, include:
- SMock Version:
dotnet list package SMock - Environment: .NET version, OS, architecture
- Minimal Reproduction: Simplest code that demonstrates the issue
- Expected vs Actual: What you expected vs what happened
- Error Messages: Full exception messages and stack traces
- Test Framework: NUnit, xUnit, MSTest version
Community Resources
- GitHub Issues: Report bugs and feature requests
- GitHub Discussions: Ask questions and share solutions
- Documentation: Complete API reference
- Examples: Real-world usage patterns
Professional Support
For enterprise users requiring professional support:
- Priority issue resolution
- Custom integration assistance
- Performance optimization consulting
- Training and onboarding
Contact: GitHub Sponsors for enterprise support options.
This troubleshooting guide should help you resolve most SMock-related issues. If you encounter problems not covered here, please contribute to the community by sharing your solution!
See Also
Quick Navigation by Issue Type
- Getting Started Issues? → Getting Started Guide - Review basics and common patterns
- Advanced Pattern Problems? → Advanced Usage Patterns - Complex scenarios and solutions
- Performance Issues? → Performance Guide - Optimization and benchmarking strategies
- Framework Integration Problems? → Testing Framework Integration - NUnit, xUnit, MSTest specific guidance
- Migration Challenges? → Migration Guide - Version upgrades and framework switching
Example-Driven Solutions
- Real-World Examples - See working solutions in practical scenarios
- API Reference - Detailed method signatures and usage examples
Community Support
- GitHub Issues - Search existing issues or report new bugs
- GitHub Discussions - Ask questions and get community help
- Stack Overflow - General programming questions with SMock tag
Preventive Resources
- Best Practices - Follow established patterns to avoid common issues
- Performance Monitoring - Set up monitoring to catch issues early