2012-06-04 41 views
20

在RhinoMocks中,您可以將您的嘲聲告訴IgnoreArguments作爲一攬子聲明。在Moq中,似乎必須爲每個參數指定It.IsAny()。但是,這不適用於ref和out參數。如何測試下面的方法,我需要MOQ內部服務調用返回一個特定的結果:如何讓Moq忽略參考或輸出的參數

public void MyMethod() { 
    // DoStuff 

    IList<SomeObject> errors = new List<SomeObject>(); 
    var result = _service.DoSomething(ref errors, ref param1, param2); 

    // Do more stuff 
} 

測試方法:

public void TestOfMyMethod() { 
    // Setup 
    var moqService = new Mock<IMyService>(); 
    IList<String> errors; 
    var model = new MyModel(); 

    // This returns null, presumably becuase "errors" 
    // here does not refer to the same object as "errors" in MyMethod 
    moqService.Setup(t => t.DoSomething(ref errors, ref model, It.IsAny<SomeType>()). 
     Returns(new OtherType())); 
} 

UPDATE:那麼,從「裁判」改變錯誤「出」的作品。所以看起來真正的問題是有一個ref參數,你不能注入。

+0

你能因爲你的樣品有3個參數'出錯誤,參數1,param2'但在測試你與兩個參數調用它'出錯誤,It.IsAny 在發佈DoSomething'的'簽名()'也許你在嘲笑一個錯誤的重載,因爲你的代碼無論如何應該工作,看[moq幫助方法部分](http://code.google。COM/P /最小起訂量/維基/快速啓動)。 – nemesv

+0

這只是一個例子 - 但我已經更新了測試並行的示例代碼 – sydneyos

回答

14

正如你已經知道問題出在你的ref的說法。

Moq當前僅支持ref參數的完全匹配,這意味着只有在您傳遞了在Setup中使用的相同實例時,纔會匹配該調用。所以沒有通用匹配,所以It.IsAny()將不起作用。

見Moq的quickstart

// ref arguments 
var instance = new Bar(); 
// Only matches if the ref argument to the invocation is the same instance 
mock.Setup(foo => foo.Submit(ref instance)).Returns(true); 

而且Moq的discussion group

價匹配意味着設置匹配只有當該方法是 稱爲與該相同的實例。 It.IsAny返回null,所以可能 不是你要找的。

使用設置中的實例與實際調用中的實例相同,並且 設置將匹配。

+1

因此,如果有嵌套的調用,這樣的ref/out變量設置內部的方法,使我打算打電話模擬,我卡住了。對於沒有這個限制的模擬框架有什麼建議嗎? – sydneyos

+0

我認爲RhinoMocks IgnoreArguments()選項應該這樣做 - 會嘗試。 – sydneyos

+0

更新,對於這些情況,我們不得不切換到RhinoMocks。實現如下所示:IList 錯誤; _repository.Stub(t => t.MethodName(out errors).OutRef(new List ())。IgnoreArguments(); – sydneyos

2

正如前面提到的@nemesv,It.IsAny返回null,因此您不能將其用作ref參數。爲了使調用工作,需要將實際的對象傳遞給它。

當您無法訪問創建要通過ref的對象時,會出現該問題。如果你有權訪問真實的對象,你可以簡單地在你的測試中使用它,而忘記試圖嘲笑它。

這是一個使用Extract和Override技術的解決方法,它可以讓你做到這一點。顧名思義,你將有問題的代碼提取到它自己的方法中。然後,從覆蓋待測試類的測試類中覆蓋該方法。最後,你設置你的真實對象,將它傳遞到你新創建的測試課程中,並根據你的需要測試你的參考調用。

這裏有很多(人爲的)代碼,但它顯示了前後的情況,並在最後通過了測試。

using System; 
using System.Collections.Generic; 
using Moq; 
using MoqRefProblem; 
using NUnit.Framework; 

namespace MoqRefProblem 
{ 
    //This class is the one we want to have passed by ref. 
    public class FileContext 
    { 
     public int LinesProcessed { get; set; } 
     public decimal AmountProcessed { get; set; } 
    } 

    public interface IRecordParser 
    { 
     //The ref parameter below is what's creating the testing problem. 
     void ParseLine(decimal amount, ref FileContext context); 
    } 

    //This is problematic because we don't have a 
    //seam that allows us to set the FileContext. 
    public class OriginalFileParser 
    { 
     private readonly IRecordParser _recordParser; 

     public OriginalFileParser(IRecordParser recordParser) 
     { 
      _recordParser = recordParser; 
     } 

     public void ParseFile(IEnumerable<decimal> items) 
     { 
      //This is the problem 
      var context = new FileContext(); 
      ParseItems(items, ref context); 
     } 

     private void ParseItems(IEnumerable<decimal> items, ref FileContext context) 
     { 
      foreach (var item in items) 
      { 
       _recordParser.ParseLine(item, ref context); 
      } 
     } 
    } 

    } 

    //This class has had the creation of the FileContext extracted into a virtual 
    //method. 
    public class FileParser 
    { 
     private readonly IRecordParser _recordParser; 

     public FileParser(IRecordParser recordParser) 
     { 
      _recordParser = recordParser; 
     } 

     public void ParseFile(IEnumerable<decimal> items) 
     { 
      //Instead of newing up a context, we'll get it from a virtual method 
      //that we'll override in a test class. 
      var context = GetFileContext(); 
      ParseItems(items, ref context); 
     } 

     //This is our extensibility point 
     protected virtual FileContext GetFileContext() 
     { 
      var context = new FileContext(); 
      return context; 
     } 

     private void ParseItems(IEnumerable<decimal> items, ref FileContext context) 
     { 
      foreach (var item in items) 
      { 
       _recordParser.ParseLine(item, ref context); 
      } 
     } 
    } 

    //Create a test class that inherits from the Class under Test  
    //We will set the FileContext object to the value we want to 
    //use. Then we override the GetContext call in the base class 
    //to return the fileContext object we just set up. 
    public class MakeTestableParser : FileParser 
    { 
     public MakeTestableParser(IRecordParser recordParser) 
      : base(recordParser) 
     { 
     } 

     private FileContext _context; 

     public void SetFileContext(FileContext context) 
     { 
      _context = context; 
     } 

     protected override FileContext GetFileContext() 
     { 
      if (_context == null) 
      { 
       throw new Exception("You must set the context before it can be used."); 
      } 

      return _context; 
     } 
    } 

[TestFixture] 
public class WorkingFileParserTest 
{ 
    [Test] 
    public void ThisWillWork() 
    { 
     //Arrange 
     var recordParser = new Mock<IRecordParser>(); 

     //Note that we are an instance of the TestableParser and not the original one. 
     var sut = new MakeTestableParser(recordParser.Object); 
     var context = new FileContext(); 
     sut.SetFileContext(context); 

     var items = new List<decimal>() 
      { 
       10.00m, 
       11.50m, 
       12.25m, 
       14.00m 
      }; 

     //Act 
     sut.ParseFile(items); 

     //Assert 
     recordParser.Verify(x => x.ParseLine(It.IsAny<decimal>(), ref context), Times.Exactly(items.Count)); 
    } 
}