Mocking replaces a real dependency with a controllable substitute that records calls and returns predetermined values. It isolates the unit under test from slow, unreliable, or side-effecting collaborators.
// Without mocking — test depends on a live email server:
public class OrderService
{
private readonly SmtpEmailSender _emailSender;
public OrderService() => _emailSender = new SmtpEmailSender(); // tight coupling
public void PlaceOrder(Order order)
{
// ... business logic ...
_emailSender.Send(order.Email, "Confirmed!"); // hits live SMTP in tests!
}
}
// With mocking — inject an interface, replace in tests:
public class OrderService
{
private readonly IEmailSender _emailSender;
public OrderService(IEmailSender emailSender) => _emailSender = emailSender;
public void PlaceOrder(Order order)
{
// ... business logic ...
_emailSender.Send(order.Email, "Confirmed!");
}
}
// In the test — Moq creates a fake IEmailSender:
var mockEmail = new Mock<IEmailSender>();
var service = new OrderService(mockEmail.Object);
service.PlaceOrder(new Order { Email = "a@b.com" });
// Verify the interaction without sending any real email:
mockEmail.Verify(e => e.Send("a@b.com", "Confirmed!"), Times.Once);
Rule of thumb: Mock collaborators at the boundary of the unit under test. Don't mock things you own and control — mock external dependencies (email, database, HTTP, clock) where real execution would slow or break tests.
Moq uses Setup(...).Returns(...) to configure what a mock returns when
a method is called with specific (or any) arguments.
using Moq;
var mockRepo = new Mock<IProductRepository>();
// Return a specific value for exact arguments:
mockRepo.Setup(r => r.GetById(42))
.Returns(new Product { Id = 42, Name = "Widget" });
// Return for ANY int argument — It.IsAny<T>():
mockRepo.Setup(r => r.GetById(It.IsAny<int>()))
.Returns(new Product { Id = 1, Name = "Fallback" });
// Return based on the argument value — It.Is<T>(predicate):
mockRepo.Setup(r => r.GetById(It.Is<int>(id => id > 0)))
.Returns<int>(id => new Product { Id = id, Name = $"Product {id}" });
// Return null (for reference types):
mockRepo.Setup(r => r.GetById(999)).Returns((Product?)null);
// Throw an exception:
mockRepo.Setup(r => r.GetById(-1))
.Throws<ArgumentOutOfRangeException>();
// Async methods — use ReturnsAsync:
var mockAsyncRepo = new Mock<IProductRepository>();
mockAsyncRepo.Setup(r => r.GetByIdAsync(42))
.ReturnsAsync(new Product { Id = 42 });
// Use the mock:
var product = mockRepo.Object.GetById(42); // returns Widget
Rule of thumb: Prefer It.IsAny<T>() for stubs where you only care about
the return value, and use exact argument matching only when the argument itself
is what the test is validating.
mock.Verify(...) asserts that a method was called after the system under
test has run. If the expectation is not met, the test fails.
var mockEmailer = new Mock<IEmailSender>();
var service = new OrderService(mockEmailer.Object);
service.PlaceOrder(new Order { Email = "user@example.com", Total = 99m });
// Was it called exactly once with these specific args?
mockEmailer.Verify(
e => e.Send("user@example.com", It.IsAny<string>()),
Times.Once);
// Was it called at least once?
mockEmailer.Verify(e => e.Send(It.IsAny<string>(), It.IsAny<string>()),
Times.AtLeastOnce);
// Was it NEVER called? (assert no interaction happened)
mockEmailer.Verify(
e => e.Send(It.IsAny<string>(), It.IsAny<string>()),
Times.Never);
// Times options:
// Times.Once — exactly 1 call
// Times.Never — 0 calls
// Times.Exactly(n) — exactly n calls
// Times.AtLeastOnce — >= 1 call
// Times.AtMost(n) — <= n calls
// Times.Between(n, m, Range.Inclusive)
// VerifyAll — fails if any Setup was not called (requires explicit setups)
mockEmailer.VerifyAll();
// VerifyNoOtherCalls — fails if any unexpected calls were made
mockEmailer.VerifyNoOtherCalls();
Rule of thumb: Verify for interactions that are the test's primary
assertion; VerifyNoOtherCalls to lock down that no unexpected side effects
occurred.
Loose (default) mocks return default values for any call without a
Setup. Strict mocks throw MockException for any call that was not
explicitly configured.
// Loose mock — unconfigured methods return default values silently
var looseMock = new Mock<IProductRepository>(); // MockBehavior.Loose (default)
looseMock.Setup(r => r.GetById(1)).Returns(new Product());
var result = looseMock.Object.GetById(99); // no setup — returns null (no exception)
looseMock.Object.Delete(99); // no setup — executes but does nothing
// Strict mock — any unconfigured call throws
var strictMock = new Mock<IProductRepository>(MockBehavior.Strict);
strictMock.Setup(r => r.GetById(1)).Returns(new Product());
var r2 = strictMock.Object.GetById(1); // OK — configured
// strictMock.Object.GetById(99); // throws MockException — not configured
// strictMock.Object.Delete(99); // throws MockException — not configured
// When strict makes sense:
// - You want to catch unexpected calls (no accidental side effects)
// - Writing exhaustive interaction tests
// When loose makes sense:
// - You only care about specific interactions
// - Setting up all paths in complex objects is noisy and fragile
// - The test is state-based (not interaction-based)
Most teams default to loose and use VerifyNoOtherCalls() selectively
when they need the strict behaviour without the global rigidity.
Rule of thumb: Start with loose mocks. Switch to strict when you are writing security- or audit-critical code where unexpected calls are a bug, not a detail.
Argument matchers let you write flexible setups and verifications that match on conditions rather than exact values.
var mock = new Mock<IOrderRepository>();
// It.IsAny<T>() — match any value of that type
mock.Setup(r => r.Save(It.IsAny<Order>())).Returns(true);
// It.Is<T>(predicate) — match a value satisfying a condition
mock.Setup(r => r.Save(It.Is<Order>(o => o.Total > 0 && o.CustomerId != Guid.Empty)))
.Returns(true);
// It.IsNotNull<T>() — any non-null value
mock.Setup(r => r.Save(It.IsNotNull<Order>())).Returns(true);
// It.IsIn / It.IsNotIn — value in a set
mock.Setup(r => r.GetByStatus(It.IsIn(OrderStatus.Pending, OrderStatus.Processing)))
.Returns(new List<Order>());
// It.IsRegex — string matching a pattern
var mockLogger = new Mock<ILogger<OrderService>>();
mockLogger.Setup(l => l.Log(
LogLevel.Error,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("failed")),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception?, string>>()));
// Capture arguments with Callback for later inspection:
Order? captured = null;
mock.Setup(r => r.Save(It.IsAny<Order>()))
.Callback<Order>(o => captured = o)
.Returns(true);
Mixing exact values and matchers in the same Setup call requires all arguments
to use matchers — you cannot mix It.IsAny<T>() with a literal in the same call.
Rule of thumb: Use exact values when the argument is what you are testing,
It.IsAny<T>() when you just need a return value, and It.Is<T>(predicate)
when partial matching is needed.
Callback lets you run custom code when a mocked method is called — most
commonly to capture arguments for later assertions or to simulate side effects.
var mockRepo = new Mock<IOrderRepository>();
var savedOrders = new List<Order>();
// Capture all saved orders:
mockRepo.Setup(r => r.Save(It.IsAny<Order>()))
.Callback<Order>(order => savedOrders.Add(order))
.Returns(true);
var service = new OrderService(mockRepo.Object);
service.PlaceOrder(new Order { Sku = "X", Quantity = 2 });
service.PlaceOrder(new Order { Sku = "Y", Quantity = 1 });
// Now inspect what was saved:
Assert.Equal(2, savedOrders.Count);
Assert.Equal("X", savedOrders[0].Sku);
// Callback before return (chained):
mockRepo.Setup(r => r.Save(It.IsAny<Order>()))
.Callback<Order>(o => Console.WriteLine($"Saving {o.Sku}"))
.Returns(true);
// Simulate a changing return value on successive calls:
var callCount = 0;
mockRepo.Setup(r => r.GetPendingCount())
.Callback(() => callCount++)
.Returns(() => callCount * 10); // returns 10 first call, 20 second, etc.
// Async version:
mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
.Callback<Order>(o => savedOrders.Add(o))
.ReturnsAsync(true);
Rule of thumb: Prefer Verify over Callback for interaction assertions —
Callback is best when you need to capture a value or inspect complex argument
state that Verify cannot express inline.
Moq supports chaining .Returns() calls (via SetupSequence) or using a
queue pattern to configure different return values per invocation.
var mockRetry = new Mock<IHttpClient>();
// SetupSequence — first call returns null, second returns a value
mockRetry.SetupSequence(c => c.GetAsync("/api/data"))
.Returns(Task.FromResult((string?)null)) // 1st call
.Returns(Task.FromResult("{ \"ok\": true }")) // 2nd call
.Throws<HttpRequestException>(); // 3rd call → exception
// Use it to test retry logic:
var client = new ResilienceClient(mockRetry.Object, maxRetries: 2);
var result = await client.GetWithRetryAsync("/api/data");
Assert.Equal("{ \"ok\": true }", result);
// Queue pattern using a list for more flexibility:
var responses = new Queue<int>(new[] { 503, 503, 200 });
var mockHttp = new Mock<IHttpClient>();
mockHttp.Setup(c => c.GetStatusCodeAsync(It.IsAny<string>()))
.ReturnsAsync(() => responses.Dequeue());
// Returns for a property that changes after a method call:
var mockContext = new Mock<IAppContext>();
var isConnected = false;
mockContext.Setup(c => c.Connect()).Callback(() => isConnected = true);
mockContext.Setup(c => c.IsConnected).Returns(() => isConnected);
Rule of thumb: Use SetupSequence for testing retry/resilience logic where
call order matters. For state that changes in response to side effects, Callback
- a captured variable is cleaner.
NSubstitute uses a leaner syntax that reads closer to plain C#. Instead of
wrapping Setup and Verify in lambda expressions, it records the actual call
and configures it in-place.
// Moq style:
var mock = new Mock<ICalculator>();
mock.Setup(c => c.Add(2, 3)).Returns(5);
var result = mock.Object.Add(2, 3);
mock.Verify(c => c.Add(2, 3), Times.Once);
// NSubstitute equivalent:
var sub = Substitute.For<ICalculator>();
sub.Add(2, 3).Returns(5); // returns configured on the actual call
var result = sub.Add(2, 3);
sub.Received(1).Add(2, 3); // verify after the fact
// Argument matchers in NSubstitute:
sub.Add(Arg.Any<int>(), Arg.Any<int>()).Returns(99);
sub.Add(Arg.Is<int>(x => x > 0), 3).Returns(42);
// Throwing from NSubstitute:
sub.Add(0, 0).Throws<DivideByZeroException>();
// Async:
var asyncSub = Substitute.For<IOrderRepository>();
asyncSub.GetByIdAsync(Arg.Any<int>()).Returns(Task.FromResult(new Order()));
// Did NOT receive — verify no call:
asyncSub.DidNotReceive().GetByIdAsync(999);
NSubstitute defaults to loose behavior (unconfigured calls return defaults).
It does not have a strict mode, but ReceivedCalls() can be inspected manually.
Rule of thumb: If you find Moq's lambda syntax noisy, try NSubstitute — smaller, cleaner setup lines with the same power. The choice is largely preference; both integrate with xUnit and produce equally reliable tests.
By default, Moq can only mock virtual/abstract public members. To mock protected or internal members, extra configuration is required.
// Mocking a protected virtual method:
public class EmailService
{
protected virtual bool IsValidEmail(string email)
=> email.Contains("@");
public bool SendConfirmation(string email)
{
if (!IsValidEmail(email)) throw new ArgumentException("Bad email");
// ...send...
return true;
}
}
// Moq — use Protected() extension:
using Moq.Protected;
var mockService = new Mock<EmailService>();
mockService.Protected()
.Setup<bool>("IsValidEmail", ItExpr.IsAny<string>())
.Returns(true);
// Internal members — use InternalsVisibleTo in production code:
// In AssemblyInfo.cs or csproj:
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Moq's proxy assembly
[assembly: InternalsVisibleTo("MyApp.Tests")]
// internal interface can then be mocked normally:
internal interface IInternalService { int Compute(); }
var mock = new Mock<IInternalService>();
mock.Setup(s => s.Compute()).Returns(42);
// Alternative: avoid mocking internals — test them through the public API,
// or extract the logic into a testable pure function.
Testing through protected members tightly couples tests to implementation. Only do this when the protected hook is an intentional extension point.
Rule of thumb: Prefer testing through the public API. If you find yourself mocking protected members frequently, consider extracting the behavior to a collaborating interface instead.
Over-mocking means replacing too many collaborators with mocks, creating tests that verify the implementation (call graph) rather than the behavior (observable outcomes). Such tests break on every refactor even when the behavior stays correct.
// Over-mocked — tests every internal call; fragile:
[Fact]
public void PlaceOrder_OverMocked()
{
var mockValidator = new Mock<IOrderValidator>();
var mockRepo = new Mock<IOrderRepository>();
var mockEmailer = new Mock<IEmailSender>();
var mockAuditLog = new Mock<IAuditLogger>();
var mockPricer = new Mock<IPricingEngine>();
mockValidator.Setup(v => v.Validate(It.IsAny<Order>())).Returns(true);
mockPricer.Setup(p => p.Price(It.IsAny<Order>())).Returns(100m);
// ...configure 3 more mocks...
// Verify ALL internal calls — test breaks if internals are refactored:
mockValidator.Verify(v => v.Validate(It.IsAny<Order>()), Times.Once);
mockPricer.Verify(p => p.Price(It.IsAny<Order>()), Times.Once);
mockAuditLog.Verify(a => a.Log(It.IsAny<string>()), Times.Once);
}
// Better — mock only external / slow / side-effecting dependencies:
[Fact]
public void PlaceOrder_FocusedMock()
{
// Only mock what crosses a process boundary or has side effects:
var mockEmailer = new Mock<IEmailSender>(); // side effect: sends email
var mockRepo = new Mock<IOrderRepository>(); // side effect: writes DB
mockRepo.Setup(r => r.Save(It.IsAny<Order>())).Returns(true);
var service = new OrderService(
new OrderValidator(), // real implementation
mockRepo.Object,
new PricingEngine(), // real implementation
mockEmailer.Object);
service.PlaceOrder(new Order { Sku = "X", Quantity = 1 });
// Assert behavior, not call graph:
mockEmailer.Verify(e => e.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
Rule of thumb: Only mock collaborators that cross a process or I/O boundary. Use real implementations for same-process logic. If replacing a real object with a mock does not make the test faster or more reliable, skip the mock.
HttpClient is a concrete class, not an interface, making it awkward to mock
directly. The standard approach is to mock the underlying HttpMessageHandler.
// Option 1: mock HttpMessageHandler (low-level but precise):
public class MockHttpMessageHandler : HttpMessageHandler
{
private readonly HttpResponseMessage _response;
public MockHttpMessageHandler(HttpResponseMessage response)
=> _response = response;
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
=> Task.FromResult(_response);
}
[Fact]
public async Task GetProduct_ReturnsDeserializedProduct()
{
var json = """{"id":1,"name":"Widget"}""";
var handler = new MockHttpMessageHandler(
new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
});
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("https://api.example.com") };
var service = new ProductService(httpClient);
var product = await service.GetProductAsync(1);
Assert.Equal("Widget", product.Name);
}
// Option 2: inject IHttpClientFactory and mock it (recommended in ASP.NET Core):
var mockFactory = new Mock<IHttpClientFactory>();
mockFactory.Setup(f => f.CreateClient(It.IsAny<string>()))
.Returns(new HttpClient(handler) { BaseAddress = new Uri("https://api.example.com") });
// Option 3: third-party — RichardSzalay.MockHttp (fluent syntax):
// var mockHttp = new MockHttpMessageHandler();
// mockHttp.When("/products/1").Respond("application/json", json);
// var client = mockHttp.ToHttpClient();
Rule of thumb: Always inject HttpClient or IHttpClientFactory via
dependency injection — never new HttpClient() inside a class. That makes
the handler swappable in tests.
Moq can mock read-only (get-only) properties on interfaces. For classes,
the property must be virtual. Concrete properties without virtual
cannot be mocked.
// Interface with get-only property — Moq handles it easily:
public interface IUserContext
{
string UserId { get; }
bool IsAdmin { get; }
}
var mockCtx = new Mock<IUserContext>();
mockCtx.Setup(c => c.UserId).Returns("user-42");
mockCtx.Setup(c => c.IsAdmin).Returns(true);
// Verify property access (rarely needed; prefer asserting behavior):
mockCtx.VerifyGet(c => c.IsAdmin, Times.Once);
// Class with virtual get-only property:
public class AppContext
{
public virtual string TenantId { get; } = "default";
}
var mockAppCtx = new Mock<AppContext>();
mockAppCtx.Setup(c => c.TenantId).Returns("tenant-99");
// Auto-property setup — SetupAllProperties() populates all settable properties:
var mockConfig = new Mock<IConfiguration>();
mockConfig.SetupAllProperties();
mockConfig.Object.ConnectionString = "..."; // now works for settable properties
// If the property is concrete and non-virtual you cannot mock it:
// Either refactor the class to use an interface,
// or use a wrapper class that implements the interface.
Rule of thumb: Design production classes to expose dependencies through interfaces. If you're fighting Moq to mock a concrete property, it's usually a sign the class needs an interface.
Moq can simulate event raising on a mocked interface so you can test code that subscribes to events without wiring up a real event source.
public interface IStockTickerService
{
event EventHandler<PriceChangedEventArgs> PriceChanged;
void Start();
}
public class PortfolioMonitor
{
private readonly IStockTickerService _ticker;
public decimal LastPrice { get; private set; }
public PortfolioMonitor(IStockTickerService ticker)
{
_ticker = ticker;
_ticker.PriceChanged += OnPriceChanged;
}
private void OnPriceChanged(object? sender, PriceChangedEventArgs e)
=> LastPrice = e.NewPrice;
}
[Fact]
public void PortfolioMonitor_WhenPriceChanges_UpdatesLastPrice()
{
var mockTicker = new Mock<IStockTickerService>();
// Subscribe the real object to the mock's event:
var monitor = new PortfolioMonitor(mockTicker.Object);
// Raise the event on the mock — passes args to all subscribers:
mockTicker.Raise(
t => t.PriceChanged += null,
new PriceChangedEventArgs { Symbol = "ACME", NewPrice = 42.50m });
Assert.Equal(42.50m, monitor.LastPrice);
// Raise multiple times to test accumulated state:
mockTicker.Raise(
t => t.PriceChanged += null,
new PriceChangedEventArgs { Symbol = "ACME", NewPrice = 55.00m });
Assert.Equal(55.00m, monitor.LastPrice);
}
For EventHandler (no custom args), pass EventArgs.Empty as the second
argument to Raise. For Action-based events (common in modern APIs),
use Raise with a matching delegate signature.
Rule of thumb: Use mock.Raise(...) to test event-driven code without
building a real event source. Keep raised events simple — complex event
choreography is a sign the subscriber needs its own unit test.
An auto-mocking container (such as AutoMocker from Moq.AutoMock or
AutoFixture + AutoMoqCustomization) automatically creates mocks for all
constructor dependencies, so tests only configure the mocks they care about.
// Without auto-mocking — boilerplate grows with every new dependency:
[Fact]
public void WithoutAutoMock_BoilerplateHeavy()
{
var mockRepo = new Mock<IOrderRepository>();
var mockEmailer = new Mock<IEmailSender>();
var mockLogger = new Mock<ILogger<OrderService>>();
var mockClock = new Mock<IClock>();
// ...and so on for every constructor param
var service = new OrderService(
mockRepo.Object, mockEmailer.Object,
mockLogger.Object, mockClock.Object);
mockRepo.Setup(r => r.Save(It.IsAny<Order>())).Returns(true);
service.PlaceOrder(new Order { Sku = "X" });
mockEmailer.Verify(e => e.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
// With AutoMocker — only touch the mocks that matter:
// dotnet add package Moq.AutoMock
[Fact]
public void WithAutoMock_LessBoilerplate()
{
var mocker = new AutoMocker();
// AutoMocker resolves all constructor params automatically:
var service = mocker.CreateInstance<OrderService>();
// Configure only what this test cares about:
mocker.GetMock<IOrderRepository>()
.Setup(r => r.Save(It.IsAny<Order>()))
.Returns(true);
service.PlaceOrder(new Order { Sku = "X" });
// Verify on the auto-created mock:
mocker.GetMock<IEmailSender>()
.Verify(e => e.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
Auto-mocking containers also insulate tests from constructor signature changes — adding a new dependency does not break existing tests that do not use that dependency.
Rule of thumb: Introduce an auto-mocking container when a class has
four or more injected dependencies and most tests only touch one or two
of them. For simpler classes, manual new Mock<T>() is less magic and
easier to read.
Moq can mock abstract classes directly — it creates a concrete subclass at
runtime. Only abstract and virtual members can be overridden; concrete
members run their real implementation.
public abstract class ReportGenerator
{
// Abstract — must be overridden; Moq will mock this:
public abstract IEnumerable<string> FetchData();
// Virtual — can be overridden in tests:
public virtual string FormatRow(string row) => row.ToUpperInvariant();
// Concrete — Moq cannot intercept this; real code runs:
public string BuildReport()
{
var rows = FetchData(); // will call mock
return string.Join("\n", rows.Select(FormatRow));
}
}
[Fact]
public void BuildReport_FormatsAllFetchedRows()
{
var mockGen = new Mock<ReportGenerator>();
// Setup the abstract method:
mockGen.Setup(g => g.FetchData())
.Returns(new[] { "alpha", "beta", "gamma" });
// CallBase = true makes concrete methods (BuildReport) execute for real:
mockGen.CallBase = true;
var report = mockGen.Object.BuildReport();
Assert.Contains("ALPHA", report);
Assert.Contains("BETA", report);
}
// Mocking only the virtual override to inject alternative formatting:
mockGen.Setup(g => g.FormatRow(It.IsAny<string>()))
.Returns<string>(r => $"[{r}]");
// Warning: do not mock concrete (non-virtual) methods —
// Moq will silently ignore the Setup and the real method runs instead.
Setting mock.CallBase = true is essential when testing a concrete method
on an abstract class — without it the concrete method returns the default
value instead of executing real logic.
Rule of thumb: Prefer interfaces over abstract classes for dependencies you intend to mock. Mock abstract classes only when the class provides shared concrete behavior (a template method pattern) that you want to test through.
More Testing interview questions
More ways to practice
The self-quiz is live. Get notified when mock interviews and new question packs drop.