[C#]Executing Custom Logic during Method Calls with Callback in Moq

目次

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 Moq is 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. Use Callback when 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 your Callback simple.

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.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次