首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Moq 框架進階:解決 ref/out 參數方法的 Callback 設定難題

     編輯:關於C語言

隨著 .NET 生態中 Mock 技術普及,Moq 憑藉 Lambda 表達式設定行為期望、捨棄傳統 Record/Replay 模式、基於 Castle DynamicProxy 實現攔截,成為主流選擇。但當被 Mock 方法包含 ref 參數時,Callback 設定卻暗藏陷阱。

問題重現

介面定義

public interface ITestInterface
{
    string TestMethodWithRef(ref string refStr, string str);
}

直觀但錯誤的寫法

[TestMethod]
public void Ref_Param_Test()
{
    var mock = new Mock();
    string refStr = "1";
    string str = "2";
    
    // 錯誤:Callback 參數不匹配
    mock.Setup(m => m.TestMethodWithRef(ref refStr, str))
        .Callback((string rs, string s) => Console.WriteLine(rs + s));
    
    mock.Object.TestMethodWithRef(ref refStr, str);
    mock.VerifyAll();
}

執行拋出:System.ArgumentException: Invalid callback. Setup on method with parameters (String&, String) cannot invoke callback with parameters (String, String)

嘗試修正:Lambda 不支援 ref/out 參數

// 編譯錯誤:Lambda 參數不能使用 ref/out
mock.Setup(m => m.TestMethodWithRef(ref refStr, str))
    .Callback((ref string rs, string s) => Console.WriteLine(rs + s));

編譯器錯誤:Variables introduced within a lambda expression are not visible in the outer method

可行方案一:捨棄 Moq,手寫 Fake 物件

直接實作介面,完全控制行為。缺點:樣板代碼多,維護成本高。

可行方案二:Callback 不傳參數,改用閉包捕獲變數

mock.Setup(m => m.TestMethodWithRef(ref refStr, str))
    .Callback(() => Console.WriteLine(refStr + str))
    .Returns("")
    .Verifiable();

透過閉包直接存取外部變數 refStr、str,繞過參數匹配限制。

隱藏陷阱:ref 參數修改在 Callback 中不生效

[TestMethod]
public void Ref_Param_Test()
{
    var mock = new Mock();
    string refStr = "1";
    string str = "1";
    
    mock.Setup(m => m.TestMethodWithRef(ref refStr, str))
        .Callback(() => { refStr = "2"; str = "2"; })
        .Returns("")
        .Verifiable();
    
    mock.Object.TestMethodWithRef(ref refStr, str);
    mock.VerifyAll();
    
    Assert.AreEqual("2", str);   // 通過
    Assert.AreEqual("2", refStr); // 失敗!refStr 仍為 "1"
}

根因分析

Moq 內部機制:Setup 時捕獲的 refStr 是當時的值副本,而非參考。Callback 中修改的是閉包捕獲的區域變數副本,不影響外部原始變數。這是 C# 閉包與 ref 語義交互的特殊行為。

最佳實務建議

  1. 避免 Mock ref/out 方法:重構介面改用回傳值或 out 參數模式
  2. 必要時手寫 Stub/Fake:完全控制參數傳遞語義
  3. 若必須用 Moq:Callback 僅做側邊效果(記錄日誌、計數),不要企圖修改 ref 參數
  4. 驗證 ref 參數變更:改用 Callback 搭配自訂類別封裝參數,或在測試後直接斷言物件狀態

此問題揭示了 Mock 框架在處理值型別語義與參考語義邊界時的限制,理解底層機制有助於設計更可測試的介面。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved