Overview
This article explains how to execute arbitrary code when a method of a mock object is called using Moq. Beyond just returning a value, the Callback method allows you to define “side effects,” such as saving the arguments passed during a call for later verification or logging messages to the console for debugging purposes.
Specifications (Input/Output)
- Input:
- The interface to be mocked (e.g., an email sending service).
- Callback: The action (delegate) to execute when the method is called.
- Output:
- The specified C# code executes as soon as the mock method is invoked.
- In this example, arguments (recipient and body) are recorded in an external list and printed to the console.
- Prerequisite: The NuGet package
Moqis required.
Basic Usage
Write .Callback(...) immediately following the Setup method. Specify generic types to match the arguments of the method.
var mock = new Mock<ILogger>();
var logs = new List<string>();
// When the Log method is called, add the message argument to the list
mock.Setup(x => x.Log(It.IsAny<string>()))
.Callback<string>(msg => logs.Add(msg));
// Execution
mock.Object.Log("Error occurred");
Full Code
The following example creates a mock for a message sending service. It saves the data to a “transmission history list” when the send method is called while returning true as the result.
using System;
using System.Collections.Generic;
using Moq;
// Requires NuGet package "Moq"
// dotnet add package Moq
namespace MoqCallbackExample
{
// Interface to be tested: Message sending functionality
public interface IMessageSender
{
bool Send(string to, string body);
}
class Program
{
static void Main()
{
// 1. Prepare a list to save transmission history for verification
var sentHistory = new List<string>();
// 2. Create the mock
var mock = new Mock<IMessageSender>();
// 3. Define behavior with Setup
// Arguments: Accepts any string (It.IsAny<string>)
mock.Setup(m => m.Send(It.IsAny<string>(), It.IsAny<string>()))
// * Callback: Processing to execute at the moment of the call
.Callback<string, string>((to, body) =>
{
string log = $"[Callback Detected] To: {to}, Body: {body}";
Console.WriteLine(log); // Console output
sentHistory.Add(log); // Save to list
})
// * Returns: The return value of the method (mocking success)
.Returns(true);
var sender = mock.Object;
Console.WriteLine("--- Execution Started ---");
// 4. Call the mock method
bool result1 = sender.Send("user@example.com", "Hello");
bool result2 = sender.Send("admin@example.com", "System Notification");
Console.WriteLine($"\nReturn Value Check: 1st={result1}, 2nd={result2}");
Console.WriteLine("\n--- Checking History List ---");
foreach (var log in sentHistory)
{
Console.WriteLine(log);
}
}
}
}
Output Example
--- Execution Started ---
[Callback Detected] To: user@example.com, Body: Hello
[Callback Detected] To: admin@example.com, Body: System Notification
Return Value Check: 1st=True, 2nd=True
--- Checking History List ---
[Callback Detected] To: user@example.com, Body: Hello
[Callback Detected] To: admin@example.com, Body: System Notification
Customization Points
Matching the Number of Arguments
- One argument:
.Callback<int>(i => ...) - Two arguments:
.Callback<string, int>((s, i) => ...) - No arguments:
.Callback(() => ...) - Supports up to 16 arguments.
Use for Exception Testing
You can describe complex state changes or test flag manipulations that cannot be expressed with Returns alone based on specific conditions.
Points of Caution
- Order of Callback and Returns: Generally, you write them in the order of
Callback(...).Returns(...). Chaining them as a method allows you to execute logic and then return a value. - Distinction from Verify: If you only need to check if a method was called with specific arguments, using
mock.Verify(m => m.Send("...", "..."), Times.Once())is more standard and cleaner. UseCallbackwhen you need to capture argument values to inspect their contents in detail. - Exception Handling: If an exception occurs inside a
Callback, it will cause the test execution itself to fail or crash. Keep the logic inside yourCallbacksimple.
Application
Inserting Logic Before and After a Call (Before/After)
While there are no strict Before/After methods like in Aspect-Oriented Programming (AOP), you can achieve similar behavior by including logic in the Returns delegate since Callback cannot follow Returns.
mock.Setup(m => m.Process())
.Callback(() => Console.WriteLine("Before processing"))
.Returns(() =>
{
Console.WriteLine("During processing (calculating return value)");
return 100;
});
Note: Since you cannot write a Callback after Returns, any post-processing must be implemented within the Returns delegate before the value is returned.
Summary
Using Callback in Moq allows for flexible processing, such as capturing arguments of mock methods or changing external states. It is a powerful tool for verifying the transfer of complex objects that cannot be fully validated by Verify alone, and for tracing logs during debugging.
