🎭 Mocking with Moq & NUnit

3️⃣ Configuring Mock Method Return Values

3.1 Configuring Mock Object Method Return Values

  • Setup cho method Validate của IIdentityVerifier:
mockIdentityVerifier.Setup(x => x.Validate("Sarah",
                                            25,
                                            "133 Pluralsight Drive, Draper, Utah"))
                    .Returns(true);

  • Nếu đổi tên "Sarah" -> "Sarah 2", method sẽ trả về false vì setup không match:
mockIdentityVerifier.Setup(x => x.Validate("Sarah 2", 25, "133 Pluralsight Drive, Draper, Utah"))
                    .Returns(true);

  • Test case đầy đủ:
[Test]
public void Accept()
{
    LoanProduct product = new LoanProduct(99, "Loan", 5.25m);
    LoanAmount amount = new LoanAmount("USD", 200_000);
    var application = new LoanApplication(42,
                                            product,
                                            amount,
                                            "Sarah",
                                            25,
                                            "133 Pluralsight Drive, Draper, Utah",
                                            65_000);

    var mockIdentityVerifier = new Mock<IIdentityVerifier>();
    mockIdentityVerifier.Setup(x => x.Validate("Sarah", 25, "133 Pluralsight Drive, Draper, Utah")).Returns(true);

    var mockCreditScorer = new Mock<ICreditScorer>();

    var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object,
                                            mockCreditScorer.Object);

    sut.Process(application);

    Assert.That(application.GetIsAccepted(), Is.True);
}


3.2 Argument Matching in Mocked Methods

  • Sử dụng It.IsAny<T>() bỏ qua giá trị cụ thể, chỉ kiểm tra kiểu:
mockIdentityVerifier.Setup(x => x.Validate(It.IsAny<string>(),
                                            It.IsAny<int>(),
                                            It.IsAny<string>()))
                    .Returns(true);

  • Test case:
[Test]
public void Accept()
{
    var mockIdentityVerifier = new Mock<IIdentityVerifier>();
    mockIdentityVerifier.Setup(x => x.Validate(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>())).Returns(true);

    var application = new LoanApplication(...);
    var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, new Mock<ICreditScorer>().Object);
    sut.Process(application);

    Assert.That(application.GetIsAccepted(), Is.True);
}

  • 💡 Lưu ý: .Returns() luôn trả về giá trị setup, không phụ thuộc parameter.

3.3 Mocking Methods with out Parameters

  • Khi function có parameter out:
void Validate(string applicantName, int applicantAge, string applicantAddress, out bool isValid);

  • Setup:
bool isValidOutValue = true;
mockIdentityVerifier.Setup(x => x.Validate("Sarah",
                                            25,
                                            "133 Pluralsight Drive, Draper, Utah",
                                            out isValidOutValue));

  • Test case đầy đủ:
[Test]
public void Accept()
{
    var mockIdentityVerifier = new Mock<IIdentityVerifier>();
    bool isValidOutValue = true;
    mockIdentityVerifier.Setup(x => x.Validate("Sarah", 25, "Address", out isValidOutValue));

    var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, new Mock<ICreditScorer>().Object);
    sut.Process(application);

    Assert.That(application.GetIsAccepted(), Is.True);
}


3.4 Mocking Methods with ref Parameters

  • Function có parameter ref:
void Validate(string applicantName, int applicantAge, string applicantAddress, ref IdentityVerificationStatus status);

  • Setup dùng delegate và .Callback():
delegate void ValidateCallback(string name, int age, string addr, ref IdentityVerificationStatus status);

mockIdentityVerifier
    .Setup(x => x.Validate("Sarah", 25, "Address", ref It.Ref.IsAny<IdentityVerificationStatus>()))
    .Callback(new ValidateCallback((string n, int a, string addr, ref IdentityVerificationStatus s) => 
                                    s = new IdentityVerificationStatus(true)));

  • Test case đầy đủ:
[Test]
public void Accept()
{
    var mockIdentityVerifier = new Mock<IIdentityVerifier>();
    mockIdentityVerifier.Setup(x => x.Validate("Sarah", 25, "Address", ref It.Ref.IsAny<IdentityVerificationStatus>()))
                        .Callback(new ValidateCallback((string n, int a, string addr, ref IdentityVerificationStatus s) => 
                                                        s = new IdentityVerificationStatus(true)));

    var sut = new LoanApplicationProcessor(mockIdentityVerifier.Object, new Mock<ICreditScorer>().Object);
    sut.Process(application);

    Assert.That(application.GetIsAccepted(), Is.True);
}


3.5 Configuring Mock Methods to Return Null

  • Interface giả:
public interface INullExample
{
    string SomeMethod();
}

  • Nếu không .Returns(), mặc định trả null:
[Test]
public void NullReturnExample()
{
    var mock = new Mock<INullExample>();
    mock.Setup(x => x.SomeMethod());

    string mockReturnValue = mock.Object.SomeMethod();
    Assert.That(mockReturnValue, Is.Null);
}


4️⃣ Working with Mock Properties

4.1 Return a Specified Value

mockCreditScorer.Setup(x => x.Score).Returns(300);

4.2 Manually Mocking Property Hierarchies

var mockScoreValue = new Mock<ScoreValue>();
mockScoreValue.Setup(x => x.Score).Returns(300);

var mockScoreResult = new Mock<ScoreResult>();
mockScoreResult.Setup(x => x.ScoreValue).Returns(mockScoreValue.Object);

var mockCreditScorer = new Mock<ICreditScorer>();
mockCreditScorer.Setup(x => x.ScoreResult).Returns(mockScoreResult.Object);

4.3 Auto Mocking Property Hierarchies

  • Nếu property là virtual, có thể viết ngắn:
mockCreditScorer.Setup(x => x.ScoreResult.ScoreValue.Score).Returns(300);

  • DefaultValue.Mock cho các property con mặc định không null:
var mockCreditScorer = new Mock<ICreditScorer> { DefaultValue = DefaultValue.Mock };

4.4 Track Changes for Property

mockCreditScorer.SetupProperty(x => x.Count);
mockCreditScorer.SetupProperty(x => x.Count, 10);

4.5 Track All Properties

mockCreditScorer.SetupAllProperties();


5️⃣ Checking That Mock Methods and Properties Are Used

5.1 Verify Method (No Parameters)

mockIdentityVerifier.Verify(x => x.Initialize());

5.2 Verify Method Called with Parameters

mockCreditScorer.Verify(x => x.CalculateScore("Sarah", "133 Pluralsight Drive, Draper, Utah"));

5.3 Verify Number of Calls

mockCreditScorer.Verify(x => x.CalculateScore("Sarah", "Address"), Times.Once);

5.4 Verify Property Get/Set

mockCreditScorer.VerifyGet(x => x.ScoreResult.ScoreValue.Score, Times.Once);
mockCreditScorer.VerifySet(x => x.Count = It.IsAny(), Times.Once);
mockCreditScorer.VerifySet(x => x.Count = 1);

5.5 Verify No Unexpected Calls

mockIdentityVerifier.VerifyNoOtherCalls();


6️⃣ Using Partial Mocks and Advanced Mocking Techniques

6.1 Understanding Strict Mocks

  • Loose mock mặc định không throw exception, Strict bắt buộc setup tất cả method.
var mockIdentityVerifier = new Mock<IIdentityVerifier>(MockBehavior.Strict);

6.2 Throwing Exceptions

mockCreditScorer.Setup(x => x.CalculateScore(It.IsAny<string>(), It.IsAny<string>()))
                .Throws(new InvalidOperationException("Test Exception"));

6.3 Raising Events

  • Moq có thể raise event từ mock object.

6.4 Understanding Partial Mocks

  • Chỉ mock một phần class, phần còn lại dùng implementation thật.

6.5 Mocking Nondeterministic Code

  • Mock DateTime.Now hoặc các giá trị random.

6.6 Mocking Protected Members

  • Partial mock có thể mock protected methods hoặc properties.

6.7 Alternatives to Partial Mocks

  • Dùng dependency injection, wrapper, hoặc helper class thay thế.

💡 Tổng kết:

  • Moq + NUnit cho phép kiểm tra unit test chi tiết, từ setup method, ref/out parameters, property tracking, verify method/property, đến xử lý exception & event.
  • Dùng đúng Strict/Loose, It.IsAny(), SetupProperty, SetupAllProperties sẽ giúp unit test chính xác và tránh lỗi ngầm.