Moq
Callback
Unit Testing
Mocking
.NET

Settings variable values in a Moq Callback call

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Callback() in Moq lets you run custom code when a mocked method is invoked, which makes it useful for capturing arguments, updating local test variables, or simulating side effects. The important detail is that the callback runs at invocation time, not at setup time, so any variables you assign there reflect the actual call made by the code under test.

Capture Arguments With Callback()

A common pattern is to save a method argument into a local variable for later assertions.

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

This is the standard answer to "how do I set variable values in a callback call": declare the variables outside the callback, then assign them inside the callback lambda.

Remember That The Variables Must Be In Outer Scope

The callback can only update variables that are visible to it.

This works:

csharp
1int callCount = 0;
2
3mock.Setup(x => x.Publish(It.IsAny<string>(), It.IsAny<string>()))
4    .Callback(() => callCount++);

Because callCount is declared outside the lambda.

That outer variable is captured by the closure, so you can inspect it after the invocation.

This is how you track:

  • how many times a method was called
  • which values were passed
  • what the latest call looked like

Combine Returns And Callback

If the mocked method returns a value, you can still use a callback.

csharp
1public interface ICalculator
2{
3    int Add(int a, int b);
4}
5
6[Fact]
7public void Uses_callback_and_return_value()
8{
9    var mock = new Mock<ICalculator>();
10    int latestA = 0;
11    int latestB = 0;
12
13    mock.Setup(x => x.Add(It.IsAny<int>(), It.IsAny<int>()))
14        .Callback<int, int>((a, b) =>
15        {
16            latestA = a;
17            latestB = b;
18        })
19        .Returns((int a, int b) => a + b);
20
21    var result = mock.Object.Add(4, 5);
22
23    Assert.Equal(9, result);
24    Assert.Equal(4, latestA);
25    Assert.Equal(5, latestB);
26}

This is useful when you want both observable side effects in the test and a meaningful return value for the code under test.

Prefer Assertions Over Complex Test Logic

Callback() is helpful, but it should not become a mini-program inside the setup. If the callback becomes full of branching logic, the test often gets harder to understand than the production code it is meant to verify.

A good callback usually does one of these:

  • capture arguments
  • increment a counter
  • mutate a tiny bit of test state

If you need much more, it may be a sign the test setup should be redesigned.

Sequence And Timing Matter

Because the callback runs when the method is invoked, the order of operations matters.

csharp
1string? captured = null;
2
3mock.Setup(x => x.Publish(It.IsAny<string>(), It.IsAny<string>()))
4    .Callback<string, string>((topic, payload) => captured = payload);
5
6Assert.Null(captured);
7mock.Object.Publish("orders", "done");
8Assert.Equal("done", captured);

This is simple, but it explains a lot of confusion. The variable is not assigned at setup time. It changes only after the mock method is actually called.

Use Verify For Call Existence, Callback For Data Capture

Do not use Callback() for everything. If the only thing you need to check is that a call happened, Verify is usually clearer.

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

Use Callback() when you need to inspect or accumulate values dynamically, not just confirm the invocation occurred.

That distinction keeps tests easier to read.

Common Pitfalls

The biggest mistake is expecting the callback to run during setup. It only runs when the mocked method is actually invoked.

Another mistake is declaring the capture variable inside the callback instead of outside it. If the test cannot see the variable later, there is nothing useful to assert.

People also overuse Callback() when Verify() would be clearer and simpler.

Finally, if the code under test calls the mock from multiple threads, updating shared variables in callbacks may need synchronization. For normal unit tests this is uncommon, but it matters in concurrent code.

Summary

  • 'Callback() runs when the mock method is invoked, not when the setup is defined.'
  • Capture variables by declaring them outside the callback and assigning them inside.
  • Use generic callback overloads to access method arguments cleanly.
  • Combine Callback() with Returns() when you need both side effects and return values.
  • Prefer Verify() when you only need to assert that a call happened.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.