Overview
This article explains “Partial Mocking,” a technique using xUnit and Moq to mock only specific methods of a class while keeping the original implementation for the remaining methods. This is effective when there is a need to isolate unstable methods—such as random number generation or getting the current time—to focus on testing the main logic that uses those results.
Specifications (Input/Output)
- Test Target: A class that generates random security codes.
- Input: The length of the code to generate (integer).
- Mock Setup: Fixing the random index generation to return a specific value or a specific sequence.
- Output: Verifying that the generated string matches the pattern defined by the mock (e.g., “AAAA” or “ABCD”).
- Technical Requirements:
- Use Moq’s
CallBase = trueto execute original implementations for methods that are not set up. - Methods to be mocked must be marked as
virtual.
- Use Moq’s
Basic Usage
To keep the original implementation while overriding only a part of the class, set CallBase = true.
// Set CallBase = true when creating the mock
var mock = new Mock<MyClass> { CallBase = true };
// Override only virtual methods
mock.Setup(m => m.GetRandomValue()).Returns(100);
// Non-overridden methods run the original implementation
var result = mock.Object.Calculate();
Full Code
This example demonstrates the xUnit test class structure. The Main method is included here only to allow a pseudo-check of the behavior in a console environment.
using System;
using System.Text;
using Xunit;
using Moq;
namespace PartialMockExample
{
class Program
{
static void Main()
{
try
{
var test = new SecurityCodeGeneratorTests();
Console.WriteLine("Test 1: Running fixed value test...");
test.Generate_ReturnsFixedString_WhenRandomIsFixed();
Console.WriteLine("-> Pass");
Console.WriteLine("Test 2: Running sequence test...");
test.Generate_ReturnsSequentialString_WhenRandomIsSequential();
Console.WriteLine("-> Pass");
}
catch (Exception ex)
{
Console.WriteLine($"-> Fail: {ex.Message}");
}
}
}
// --- Test Class ---
public class SecurityCodeGeneratorTests
{
private readonly Mock<SecurityCodeGenerator> _mock;
public SecurityCodeGeneratorTests()
{
// IMPORTANT: Setting CallBase = true ensures non-setup methods
// execute the original class implementation.
_mock = new Mock<SecurityCodeGenerator> { CallBase = true };
}
[Fact]
public void Generate_ReturnsFixedString_WhenRandomIsFixed()
{
// Arrange
// Fix GetRandomIndex to always return 0 (the first character in the set)
_mock.Setup(x => x.GetRandomIndex(It.IsAny<int>()))
.Returns(0);
var generator = _mock.Object;
// Act
// The Generate method itself runs its original implementation
var result = generator.Generate(4);
// Assert
// "A" (index 0) should be selected 4 times from the character set
Assert.Equal("AAAA", result);
}
[Fact]
public void Generate_ReturnsSequentialString_WhenRandomIsSequential()
{
// Arrange
// Return 0, 1, 2, 3 in order for each call
_mock.SetupSequence(x => x.GetRandomIndex(It.IsAny<int>()))
.Returns(0)
.Returns(1)
.Returns(2)
.Returns(3);
var generator = _mock.Object;
// Act
var result = generator.Generate(4);
// Assert
// Indices 0, 1, 2, 3 should be selected in order
Assert.Equal("ABCD", result);
}
}
// --- Class to be Tested ---
public class SecurityCodeGenerator
{
private readonly string _charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
// Main logic to be tested without mocking
public string Generate(int length)
{
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
// The key point is calling a virtual method internally
int index = GetRandomIndex(_charSet.Length);
sb.Append(_charSet[index]);
}
return sb.ToString();
}
// Random number generation part
// IMPORTANT: Must be virtual for Moq to override it
public virtual int GetRandomIndex(int max)
{
// Returns a random value in production
return Random.Shared.Next(max);
}
}
}
Output Example
Test 1: Running fixed value test...
-> Pass
Test 2: Running sequence test...
-> Pass
Customization Points
- Visibility of Target Methods: To allow Moq to override a method, it must be
public virtualorprotected virtual(usingProtected().Setup(...)). If making a methodvirtualis not desirable, consider extracting an interface, such asIRandomProvider. - Non-Random Use Cases: Time-dependent logic (e.g., expiration date checks) can be tested by creating a virtual method that wraps
DateTime.Nowand configuring it to return a specific date only during tests.
Points of Caution
- Forgetting CallBase = true: If this setting is omitted, any method that is not set up (such as the
Generatemethod under test) will return default values (e.g.,null), making it impossible to perform a valid test. - Impact on Design: Making methods
virtualsolely for testing might weaken encapsulation. Partial Mocking should be considered a solution for legacy code. For cleaner designs, it is better to separate the random generation logic into a different interface and use Dependency Injection (DI). - Constructor Arguments: If the target class has a constructor with arguments, those arguments must be passed to the Mock constructor, such as
new Mock<TargetClass>(arg1, arg2).
Application
Mocking Protected Methods
If there is a need to mock internal logic that is not public (protected virtual), use the Moq.Protected namespace.
using Moq.Protected;
// Mocking 'protected virtual int GetInternalValue()'
mock.Protected()
.Setup<int>("GetInternalValue")
.Returns(99);
Summary
Implementing a Partial Mock with xUnit and Moq is possible by combining virtual methods with CallBase = true. This enables strict unit testing of logic even in classes containing uncertain elements like random numbers or time. Use this method appropriately, as overusing it can lead to a class design driven too heavily by testing requirements.
