Moq
Callback
Unit Testing
Mocking
.NET

Can you help me understand Moq Callback?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Moq, Callback lets you run custom code when a mocked method is invoked. It is most useful when you want to capture arguments, simulate side effects, or inspect how the system under test interacts with a dependency beyond a simple "was this method called" check.

The key idea is that Callback attaches behavior to a setup. It does not replace Returns or Verify; it complements them when the test needs to observe something happening at call time.

Start with a Simple Setup

Suppose a service sends a message through a dependency:

csharp
1public interface IMessageBus
2{
3    void Publish(string topic, string payload);
4}

You can use Callback to capture the values passed in:

csharp
1using Moq;
2using Xunit;
3
4public class MessageTests
5{
6    [Fact]
7    public void Publish_uses_expected_values()
8    {
9        var bus = new Mock<IMessageBus>();
10        string capturedTopic = null;
11        string capturedPayload = null;
12
13        bus.Setup(x => x.Publish(It.IsAny<string>(), It.IsAny<string>()))
14           .Callback<string, string>((topic, payload) =>
15           {
16               capturedTopic = topic;
17               capturedPayload = payload;
18           });
19
20        bus.Object.Publish("orders", "created");
21
22        Assert.Equal("orders", capturedTopic);
23        Assert.Equal("created", capturedPayload);
24    }
25}

This is helpful when you want to inspect arguments after the call, especially if the assertion logic is easier to read outside the setup expression.

Callback Runs When the Method Is Invoked

Callback executes at the moment the mocked member is called. That means it is good for:

  • capturing method arguments
  • incrementing counters
  • mutating external test state
  • simulating side effects such as storing data in a fake collection

For example, a repository mock can append saved items to a test list:

csharp
1public interface IRepository
2{
3    void Save(string value);
4}
5
6var saved = new List<string>();
7var repository = new Mock<IRepository>();
8
9repository.Setup(x => x.Save(It.IsAny<string>()))
10          .Callback<string>(value => saved.Add(value));
11
12repository.Object.Save("alpha");
13repository.Object.Save("beta");
14
15Assert.Equal(2, saved.Count);

That makes the mock behave a little like a tiny in-memory fake while still being configured with Moq.

Callback and Returns Often Work Together

For methods that return a value, Callback and Returns can be chained:

csharp
1public interface ICalculator
2{
3    int Add(int left, int right);
4}
5
6var calls = 0;
7var calculator = new Mock<ICalculator>();
8
9calculator.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>()))
10          .Callback(() => calls++)
11          .Returns<int, int>((left, right) => left + right);
12
13int result = calculator.Object.Add(2, 3);
14
15Assert.Equal(5, result);
16Assert.Equal(1, calls);

Here Callback records that the call happened, while Returns provides the result.

When Verify Is Enough

Do not use Callback just to prove a method was called once with certain arguments. Moq already has Verify for that:

csharp
bus.Verify(x => x.Publish("orders", "created"), Times.Once);

Verify is usually cleaner for pure interaction assertions. Reach for Callback when the test needs to capture, transform, or accumulate data during invocation.

That distinction keeps tests readable. If every expectation is implemented through side effects in a callback, the test becomes harder to scan.

Use It Carefully

Because Callback can mutate outside state, it is easy to write brittle tests with too much hidden behavior. A good rule is:

  • use Verify for straightforward interaction assertions
  • use Returns for outputs
  • use Callback only for call-time side effects the test genuinely needs

That keeps mocks focused and prevents a setup chain from turning into a mini program.

Common Pitfalls

  • Using Callback when Verify would express the assertion more clearly.
  • Forgetting that the generic parameters in Callback<T1, T2> must match the mocked method signature.
  • Hiding too much test logic inside callbacks and making tests hard to understand.
  • Expecting Callback to return a value. It performs side effects; Returns controls the result.

Summary

  • 'Callback runs custom code when a mocked member is invoked.'
  • It is useful for capturing arguments and simulating side effects.
  • 'Callback often pairs with Returns but serves a different purpose.'
  • Use Verify when you only need to assert that a call happened.
  • Keep callbacks small so the mock setup stays readable and predictable.

Course illustration
Course illustration

All Rights Reserved.