Skip to content

Mocking Strategies in ConnectSoft Microservice Template

Purpose & Overview

Mocking Strategies in the ConnectSoft Microservice Template provide techniques for isolating units under test by replacing dependencies with test doubles. Effective mocking enables fast, reliable unit tests while maintaining confidence in code correctness. The template uses Moq as the primary mocking framework, supporting various test double patterns including mocks, stubs, fakes, and spies.

Mocking strategies provide:

  • Isolation: Test units in isolation without external dependencies
  • Speed: Fast test execution without I/O operations
  • Reliability: Deterministic tests that don't depend on external systems
  • Flexibility: Control dependencies' behavior for various test scenarios
  • Verification: Verify interactions between components
  • Testability: Enable testing of complex scenarios and edge cases

Mocking Philosophy

Mocking is a tool for achieving test isolation, not a goal in itself. The template emphasizes using appropriate test doubles for the right scenarios—mocks for verifying interactions, stubs for providing data, fakes for simplified implementations, and spies for observing behavior. Choose the simplest test double that satisfies the test requirements.

Test Double Types

Test Double Hierarchy

Test Double (Generic term)
├── Dummy (not used, just to fill parameters)
├── Stub (provides predefined responses)
├── Spy (records interactions for verification)
├── Mock (expects specific calls and verifies them)
└── Fake (working implementation with limitations)

Mocks

Purpose: Verify interactions and enforce expectations.

Characteristics: - Expects specific method calls - Verifies call counts and parameters - Fails if expectations aren't met - Used for behavior verification

Example:

// Arrange
var mockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
mockRepository.Setup(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()))
    .Verifiable();

var processor = new DefaultMicroserviceAggregateRootsProcessor(
    logger, timeProvider, mockRepository.Object, unitOfWork, eventBus, validator, metrics);

// Act
await processor.CreateMicroserviceAggregateRoot(input);

// Assert
mockRepository.Verify(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()), Times.Once);

Stubs

Purpose: Provide predefined responses without verification.

Characteristics: - Returns fixed values or executes simple logic - No verification of interactions - Used for state verification - Focuses on output, not behavior

Example:

// Arrange
var stubRepository = new Mock<IMicroserviceAggregateRootsRepository>();
stubRepository.Setup(r => r.GetByIdAsync(It.IsAny<Guid>()))
    .ReturnsAsync(new MicroserviceAggregateRootEntity 
    { 
        ObjectId = testId,
        SomeValue = "Test Value"
    });

var processor = new DefaultMicroserviceAggregateRootsProcessor(
    logger, timeProvider, stubRepository.Object, unitOfWork, eventBus, validator, metrics);

// Act
var result = await processor.GetMicroserviceAggregateRootDetails(input);

// Assert
Assert.AreEqual("Test Value", result.SomeValue);

Fakes

Purpose: Provide working implementations with limitations.

Characteristics: - Real implementation with simplified behavior - Suitable for integration-style tests - May have limitations (e.g., in-memory storage) - Can be shared across multiple tests

Example:

// Fake implementation
public class InMemoryMicroserviceAggregateRootsRepository : IMicroserviceAggregateRootsRepository
{
    private readonly List<IMicroserviceAggregateRoot> entities = new();

    public Task<IMicroserviceAggregateRoot?> GetByIdAsync(Guid id, CancellationToken token = default)
    {
        return Task.FromResult(entities.FirstOrDefault(e => e.ObjectId == id));
    }

    public Task InsertAsync(IMicroserviceAggregateRoot entity, CancellationToken token = default)
    {
        entities.Add(entity);
        return Task.CompletedTask;
    }
}

// Usage in tests
[TestMethod]
public async Task Create_Should_Persist_Entity()
{
    // Arrange
    var fakeRepository = new InMemoryMicroserviceAggregateRootsRepository();
    var processor = new DefaultMicroserviceAggregateRootsProcessor(
        logger, timeProvider, fakeRepository, unitOfWork, eventBus, validator, metrics);

    // Act
    var result = await processor.CreateMicroserviceAggregateRoot(input);

    // Assert
    var retrieved = await fakeRepository.GetByIdAsync(result.ObjectId);
    Assert.IsNotNull(retrieved);
}

Spies

Purpose: Record interactions for later verification.

Characteristics: - Records method calls and parameters - Allows verification after execution - More flexible than strict mocks - Useful for complex interaction verification

Example:

// Arrange
var spyRepository = new Mock<IMicroserviceAggregateRootsRepository>();
var calledMethods = new List<string>();
var callParameters = new List<object>();

spyRepository.Setup(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()))
    .Callback<IMicroserviceAggregateRoot>(entity =>
    {
        calledMethods.Add(nameof(IMicroserviceAggregateRootsRepository.Insert));
        callParameters.Add(entity);
    });

// Act
await processor.CreateMicroserviceAggregateRoot(input);

// Assert
Assert.AreEqual(1, calledMethods.Count);
Assert.AreEqual(nameof(IMicroserviceAggregateRootsRepository.Insert), calledMethods[0]);
Assert.IsInstanceOfType(callParameters[0], typeof(IMicroserviceAggregateRoot));

Moq Framework

Basic Usage

Creating Mocks:

// Full mock
var mock = new Mock<IMicroserviceAggregateRootsRepository>();

// Lightweight mock (using Mock.Of<T>)
var repository = Mock.Of<IMicroserviceAggregateRootsRepository>();

Setting Up Behavior:

// Return value
mock.Setup(r => r.GetByIdAsync(It.IsAny<Guid>()))
    .ReturnsAsync(new MicroserviceAggregateRootEntity());

// Return value with parameters
mock.Setup(r => r.GetByIdAsync(It.Is<Guid>(id => id == testId)))
    .ReturnsAsync(expectedEntity);

// Throw exception
mock.Setup(r => r.GetByIdAsync(It.IsAny<Guid>()))
    .ThrowsAsync(new NotFoundException());

// Async method with cancellation token
mock.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(new MicroserviceAggregateRootEntity());

Verifying Interactions:

// Verify method was called
mock.Verify(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()), Times.Once);

// Verify method was never called
mock.Verify(r => r.Delete(It.IsAny<Guid>()), Times.Never);

// Verify method was called with specific parameters
mock.Verify(r => r.GetByIdAsync(It.Is<Guid>(id => id == testId)), Times.Once);

// Verify all setups
mock.VerifyAll();

Advanced Moq Patterns

Property Setup

// Setup property getter
mock.Setup(r => r.Count)
    .Returns(10);

// Setup property setter
mock.SetupSet(r => r.Name = "Test")
    .Verifiable();

// Setup property with backing field
mock.SetupProperty(r => r.Name, "Initial Value");

Callback

// Capture parameters
IMicroserviceAggregateRoot? capturedEntity = null;
mock.Setup(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()))
    .Callback<IMicroserviceAggregateRoot>(entity => capturedEntity = entity);

// Execute custom logic
mock.Setup(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()))
    .Callback<IMicroserviceAggregateRoot>(entity =>
    {
        // Custom validation or logging
        Assert.IsNotNull(entity);
    });

Sequences

// Return different values on subsequent calls
mock.SetupSequence(r => r.GetByIdAsync(It.IsAny<Guid>()))
    .ReturnsAsync(entity1)
    .ReturnsAsync(entity2)
    .ThrowsAsync(new NotFoundException());

Protected Members

// Mock protected methods (requires Moq.Protected)
var mockHttpHandler = new Mock<HttpMessageHandler>();
mockHttpHandler
    .Protected()
    .Setup<Task<HttpResponseMessage>>(
        "SendAsync",
        ItExpr.IsAny<HttpRequestMessage>(),
        ItExpr.IsAny<CancellationToken>())
    .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK });

Mocking Patterns by Component

Repository Mocking

Unit Testing Processors:

[TestMethod]
public async Task Create_WhenAlreadyExists_ThrowsException()
{
    // Arrange
    var mockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
    mockRepository.Setup(r => r.GetByIdAsync(It.IsAny<Guid>()))
        .ReturnsAsync(new MicroserviceAggregateRootEntity()); // Entity exists

    var mockUnitOfWork = new Mock<IUnitOfWork>();
    mockUnitOfWork.Setup(u => u.ExecuteTransactional(It.IsAny<Action>()))
        .Callback<Action>(action => action());

    var processor = new DefaultMicroserviceAggregateRootsProcessor(
        Mock.Of<ILogger<DefaultMicroserviceAggregateRootsProcessor>>(),
        TimeProvider.System,
        mockRepository.Object,
        mockUnitOfWork.Object,
        Mock.Of<IEventBus>(),
        Mock.Of<IValidator<CreateMicroserviceAggregateRootInput>>(),
        Mock.Of<IValidator<DeleteMicroserviceAggregateRootInput>>(),
        Mock.Of<MicroserviceTemplateMetrics>());

    var input = new CreateMicroserviceAggregateRootInput 
    { 
        ObjectId = Guid.NewGuid() 
    };

    // Act & Assert
    await Assert.ThrowsExceptionAsync<MicroserviceAggregateRootAlreadyExistsException>(
        () => processor.CreateMicroserviceAggregateRoot(input));
}

Verifying Repository Calls:

[TestMethod]
public async Task Create_Should_Insert_Into_Repository()
{
    // Arrange
    var mockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
    mockRepository.Setup(r => r.GetByIdAsync(It.IsAny<Guid>()))
        .ReturnsAsync((IMicroserviceAggregateRoot?)null); // Entity doesn't exist

    var processor = new DefaultMicroserviceAggregateRootsProcessor(
        logger, timeProvider, mockRepository.Object, unitOfWork, eventBus, validator, metrics);

    // Act
    var result = await processor.CreateMicroserviceAggregateRoot(input);

    // Assert
    mockRepository.Verify(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()), Times.Once);
    mockRepository.Verify(r => r.GetByIdAsync(It.IsAny<Guid>()), Times.Once);
}

Service Mocking

Mocking External Services:

[TestMethod]
public async Task ProcessPayment_WithValidRequest_ShouldSucceed()
{
    // Arrange
    var mockPaymentService = new Mock<IPaymentService>();
    mockPaymentService.Setup(s => s.ProcessPaymentAsync(It.IsAny<PaymentRequest>()))
        .ReturnsAsync(new PaymentResult 
        { 
            Success = true, 
            TransactionId = "txn-123" 
        });

    var processor = new PaymentProcessor(
        mockPaymentService.Object,
        Mock.Of<ILogger<PaymentProcessor>>());

    // Act
    var result = await processor.ProcessPayment(new PaymentInput { Amount = 100 });

    // Assert
    Assert.IsTrue(result.Success);
    Assert.AreEqual("txn-123", result.TransactionId);
}

Mocking Logger:

// Using Mock.Of<T> for simple cases
var logger = Mock.Of<ILogger<MyService>>();

// Using Mock<T> for verification
var mockLogger = new Mock<ILogger<MyService>>();
var logMessages = new List<string>();

mockLogger.Setup(l => l.Log(
    It.IsAny<LogLevel>(),
    It.IsAny<EventId>(),
    It.IsAny<It.IsAnyType>(),
    It.IsAny<Exception>(),
    It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
    .Callback<LogLevel, EventId, object, Exception, Func<object, Exception?, string>>(
        (level, eventId, state, ex, formatter) =>
        {
            logMessages.Add(formatter(state, ex));
        });

// Verify logging
mockLogger.Verify(
    x => x.Log(
        LogLevel.Error,
        It.IsAny<EventId>(),
        It.Is<It.IsAnyType>((v, t) => true),
        It.IsAny<Exception>(),
        It.Is<Func<It.IsAnyType, Exception?, string>>((v, t) => true)),
    Times.Once);

HTTP Client Mocking

Mocking HttpMessageHandler:

[TestMethod]
public async Task GetData_WithValidResponse_ShouldReturnData()
{
    // Arrange
    var mockHttpHandler = new Mock<HttpMessageHandler>();
    mockHttpHandler
        .Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.Is<HttpRequestMessage>(req => req.Method == HttpMethod.Get),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(new HttpResponseMessage
        {
            StatusCode = HttpStatusCode.OK,
            Content = new StringContent("{\"data\": \"test\"}", Encoding.UTF8, "application/json")
        });

    var httpClient = new HttpClient(mockHttpHandler.Object);
    var service = new ExternalServiceClient(httpClient, Mock.Of<ILogger<ExternalServiceClient>>());

    // Act
    var result = await service.GetDataAsync();

    // Assert
    Assert.AreEqual("test", result.Data);
}

Mocking IHttpClientFactory:

[TestMethod]
public async Task ProcessRequest_ShouldUseHttpClient()
{
    // Arrange
    var mockHandler = new Mock<HttpMessageHandler>();
    mockHandler
        .Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.IsAny<HttpRequestMessage>(),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK });

    var mockHttpClientFactory = new Mock<IHttpClientFactory>();
    mockHttpClientFactory.Setup(f => f.CreateClient(It.IsAny<string>()))
        .Returns(new HttpClient(mockHandler.Object));

    var service = new MyService(
        mockHttpClientFactory.Object,
        Mock.Of<ILogger<MyService>>());

    // Act
    await service.ProcessRequestAsync();

    // Assert
    mockHttpClientFactory.Verify(f => f.CreateClient("MyService"), Times.Once);
}

Message Bus Mocking

Mocking Event Bus:

[TestMethod]
public async Task Create_Should_Publish_Event()
{
    // Arrange
    var mockEventBus = new Mock<IEventBus>();
    var publishedEvents = new List<object>();

    mockEventBus.Setup(b => b.PublishEvent(
            It.IsAny<MicroserviceAggregateRootCreatedEvent>(),
            It.IsAny<CancellationToken>()))
        .Callback<MicroserviceAggregateRootCreatedEvent, CancellationToken>((evt, ct) =>
        {
            publishedEvents.Add(evt);
        })
        .Returns(Task.CompletedTask);

    var processor = new DefaultMicroserviceAggregateRootsProcessor(
        logger, timeProvider, repository, unitOfWork, mockEventBus.Object, validator, metrics);

    // Act
    await processor.CreateMicroserviceAggregateRoot(input);

    // Assert
    Assert.AreEqual(1, publishedEvents.Count);
    Assert.IsInstanceOfType(publishedEvents[0], typeof(MicroserviceAggregateRootCreatedEvent));
    mockEventBus.Verify(b => b.PublishEvent(
        It.IsAny<MicroserviceAggregateRootCreatedEvent>(),
        It.IsAny<CancellationToken>()), Times.Once);
}

Configuration Mocking

Mocking IConfiguration:

[TestMethod]
public void GetConfiguration_ShouldReturnValue()
{
    // Arrange
    var mockConfiguration = new Mock<IConfiguration>();
    mockConfiguration.Setup(c => c["Microservice:MicroserviceName"])
        .Returns("TestService");

    var service = new ConfigurationService(mockConfiguration.Object);

    // Act
    var name = service.GetServiceName();

    // Assert
    Assert.AreEqual("TestService", name);
}

// Alternative: Use in-memory configuration
[TestMethod]
public void GetConfiguration_WithInMemoryConfig_ShouldReturnValue()
{
    // Arrange
    var config = new ConfigurationBuilder()
        .AddInMemoryCollection(new Dictionary<string, string?>
        {
            ["Microservice:MicroserviceName"] = "TestService"
        })
        .Build();

    var service = new ConfigurationService(config);

    // Act
    var name = service.GetServiceName();

    // Assert
    Assert.AreEqual("TestService", name);
}

Options Mocking

Mocking IOptions:

[TestMethod]
public void GetOptions_ShouldReturnConfiguredValue()
{
    // Arrange
    var options = new MicroserviceOptions
    {
        MicroserviceName = "TestService",
        StartupWarmupSeconds = 30
    };

    var mockOptions = new Mock<IOptions<MicroserviceOptions>>();
    mockOptions.Setup(o => o.Value)
        .Returns(options);

    var service = new MyService(mockOptions.Object);

    // Act
    var name = service.GetServiceName();

    // Assert
    Assert.AreEqual("TestService", name);
}

Integration Testing Mocking

Mocking in WebApplicationFactory

Replace Services Per Test:

[TestMethod]
public async Task Test_WithMockedService()
{
    var mockService = new Mock<IExternalService>();
    mockService.Setup(s => s.GetData())
        .ReturnsAsync("Test Data");

    var client = this.factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureServices(services =>
        {
            services.RemoveAll<IExternalService>();
            services.AddSingleton(mockService.Object);
        });
    }).CreateClient();

    // Test with mocked service
    var response = await client.GetAsync("/api/data");
    var content = await response.Content.ReadAsStringAsync();
    Assert.Contains("Test Data", content);
}

Mocking HTTP Clients in Integration Tests:

[TestMethod]
public async Task Test_WithMockedHttpClient()
{
    var mockHttpHandler = new Mock<HttpMessageHandler>();
    mockHttpHandler
        .Protected()
        .Setup<Task<HttpResponseMessage>>(
            "SendAsync",
            ItExpr.IsAny<HttpRequestMessage>(),
            ItExpr.IsAny<CancellationToken>())
        .ReturnsAsync(new HttpResponseMessage
        {
            StatusCode = HttpStatusCode.OK,
            Content = new StringContent("{\"data\": \"test\"}")
        });

    var client = this.factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureServices(services =>
        {
            services.RemoveAll<IHttpClientFactory>();
            services.AddSingleton<IHttpClientFactory>(sp =>
            {
                var factory = new Mock<IHttpClientFactory>();
                factory.Setup(f => f.CreateClient(It.IsAny<string>()))
                    .Returns(new HttpClient(mockHttpHandler.Object));
                return factory.Object;
            });
        });
    }).CreateClient();

    // Test with mocked HTTP client
}

Best Practices

Do's

  1. Use Appropriate Test Doubles

    // ✅ GOOD - Use stub for data, mock for verification
    var stubRepository = new Mock<IMicroserviceAggregateRootsRepository>();
    stubRepository.Setup(r => r.GetByIdAsync(It.IsAny<Guid>()))
        .ReturnsAsync(entity);
    
    var mockEventBus = new Mock<IEventBus>();
    // ... setup ...
    mockEventBus.Verify(b => b.PublishEvent(...), Times.Once);
    

  2. Use Mock.Of for Simple Cases

    // ✅ GOOD - Simple dependencies
    var logger = Mock.Of<ILogger<MyService>>();
    var timeProvider = TimeProvider.System;
    

  3. Verify Important Interactions

    // ✅ GOOD - Verify critical behavior
    mockRepository.Verify(r => r.Insert(It.IsAny<IMicroserviceAggregateRoot>()), Times.Once);
    mockEventBus.Verify(b => b.PublishEvent(...), Times.Once);
    

  4. Isolate Test Setup

    // ✅ GOOD - Clear, focused setup
    [TestInitialize]
    public void Setup()
    {
        this.mockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
        this.processor = new DefaultMicroserviceAggregateRootsProcessor(
            Mock.Of<ILogger<...>>(),
            TimeProvider.System,
            this.mockRepository.Object,
            // ... other dependencies
        );
    }
    

  5. Use Real Implementations When Possible

    // ✅ GOOD - Use real simple dependencies
    var validator = new CreateMicroserviceAggregateRootInputValidator();
    var timeProvider = TimeProvider.System;
    
    // Only mock complex dependencies
    var mockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
    

Don'ts

  1. Don't Over-Mock

    // ❌ BAD - Mocking everything
    var mockLogger = new Mock<ILogger<MyService>>();
    var mockTimeProvider = new Mock<TimeProvider>();
    var mockValidator = new Mock<IValidator<Input>>();
    
    // ✅ GOOD - Mock only what's necessary
    var logger = Mock.Of<ILogger<MyService>>();
    var timeProvider = TimeProvider.System;
    var validator = new InputValidator();
    

  2. Don't Verify Unnecessary Interactions

    // ❌ BAD - Verifying implementation details
    mockLogger.Verify(l => l.Log(LogLevel.Information, ...), Times.Exactly(3));
    
    // ✅ GOOD - Verify important behavior
    mockRepository.Verify(r => r.Insert(...), Times.Once);
    

  3. Don't Use Mocks for Simple Value Objects

    // ❌ BAD - Mocking simple objects
    var mockInput = new Mock<CreateMicroserviceAggregateRootInput>();
    mockInput.Setup(i => i.ObjectId).Returns(Guid.NewGuid());
    
    // ✅ GOOD - Use real objects
    var input = new CreateMicroserviceAggregateRootInput 
    { 
        ObjectId = Guid.NewGuid() 
    };
    

  4. Don't Share Mock State Between Tests

    // ❌ BAD - Shared mocks
    private static Mock<IRepository> sharedMock = new Mock<IRepository>();
    
    // ✅ GOOD - Fresh mocks per test
    [TestInitialize]
    public void Setup()
    {
        this.mockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
    }
    

  5. Don't Mock What You're Testing

    // ❌ BAD - Mocking the system under test
    var mockProcessor = new Mock<IMicroserviceAggregateRootsProcessor>();
    
    // ✅ GOOD - Mock dependencies, not the SUT
    var processor = new DefaultMicroserviceAggregateRootsProcessor(
        logger, timeProvider, mockRepository.Object, ...);
    

Common Patterns

Test Fixtures

Creating Reusable Test Fixtures:

public class ProcessorTestFixture
{
    public Mock<IMicroserviceAggregateRootsRepository> MockRepository { get; }
    public Mock<IUnitOfWork> MockUnitOfWork { get; }
    public Mock<IEventBus> MockEventBus { get; }
    public DefaultMicroserviceAggregateRootsProcessor Processor { get; }

    public ProcessorTestFixture()
    {
        this.MockRepository = new Mock<IMicroserviceAggregateRootsRepository>();
        this.MockUnitOfWork = new Mock<IUnitOfWork>();
        this.MockEventBus = new Mock<IEventBus>();

        this.Processor = new DefaultMicroserviceAggregateRootsProcessor(
            Mock.Of<ILogger<DefaultMicroserviceAggregateRootsProcessor>>(),
            TimeProvider.System,
            this.MockRepository.Object,
            this.MockUnitOfWork.Object,
            this.MockEventBus.Object,
            Mock.Of<IValidator<CreateMicroserviceAggregateRootInput>>(),
            Mock.Of<IValidator<DeleteMicroserviceAggregateRootInput>>(),
            Mock.Of<MicroserviceTemplateMetrics>());
    }

    public void SetupEntityExists(Guid id)
    {
        this.MockRepository.Setup(r => r.GetByIdAsync(id))
            .ReturnsAsync(new MicroserviceAggregateRootEntity { ObjectId = id });
    }

    public void SetupEntityNotFound(Guid id)
    {
        this.MockRepository.Setup(r => r.GetByIdAsync(id))
            .ReturnsAsync((IMicroserviceAggregateRoot?)null);
    }
}

Builder Pattern for Test Data

public class CreateMicroserviceAggregateRootInputBuilder
{
    private Guid objectId = Guid.NewGuid();

    public CreateMicroserviceAggregateRootInputBuilder WithObjectId(Guid id)
    {
        this.objectId = id;
        return this;
    }

    public CreateMicroserviceAggregateRootInput Build()
    {
        return new CreateMicroserviceAggregateRootInput
        {
            ObjectId = this.objectId
        };
    }
}

// Usage
var input = new CreateMicroserviceAggregateRootInputBuilder()
    .WithObjectId(testId)
    .Build();

Troubleshooting

Issue: Mock Setup Not Working

Symptoms: Mock returns null or default values instead of configured behavior.

Solutions: 1. Verify mock setup is before the method call 2. Check parameter matching (use It.IsAny<T>() if unsure) 3. Ensure async methods use ReturnsAsync() instead of Returns() 4. Verify mock object is passed correctly (use .Object property)

Issue: Verification Failing

Symptoms: Verify() calls fail unexpectedly.

Solutions: 1. Check call count expectations (use Times.AtLeast() if unsure) 2. Verify parameter matching (exact match vs It.IsAny<>()) 3. Ensure method was actually called (check for exceptions) 4. Use VerifyAll() to see all verification failures

Issue: Protected Members Not Mockable

Symptoms: Can't mock protected methods or properties.

Solutions: 1. Use Moq.Protected namespace 2. Use ItExpr instead of It for parameter matching 3. Use string method name for protected methods 4. Consider exposing protected members through public interface if needed

Issue: Mock State Between Tests

Symptoms: Tests fail when run together but pass individually.

Solutions: 1. Create fresh mocks in [TestInitialize] or test method 2. Use Mock.Reset() between tests if needed 3. Avoid static mock fields 4. Use [TestCleanup] to reset state if necessary

Summary

Mocking strategies in the ConnectSoft Microservice Template provide:

  • Test Isolation: Isolate units under test from dependencies
  • Fast Tests: Execute tests without I/O operations
  • Flexible Testing: Test various scenarios and edge cases
  • Interaction Verification: Verify component interactions
  • Multiple Patterns: Support for mocks, stubs, fakes, and spies
  • Moq Integration: Comprehensive Moq framework support
  • Best Practices: Guidelines for effective mocking

By following these patterns, teams can:

  • Write Reliable Tests: Create tests that don't depend on external systems
  • Test Edge Cases: Easily simulate error conditions and unusual scenarios
  • Verify Interactions: Ensure components interact correctly
  • Maintain Testability: Keep code testable through dependency injection
  • Speed Up Tests: Run tests quickly without external dependencies

Effective mocking strategies ensure that tests are fast, reliable, and maintainable, enabling teams to achieve high code coverage and confidence in their codebase while maintaining clean architecture principles.