2014-02-13 135 views
5

我有以下測試,支持類,但我無法弄清楚如何驗證依賴關係上的調用。Moq驗證與匿名類型參數

[TestFixture] 
public class AnonymousGenericTypeParameterTests 
{ 
    [Test] 
    public void Test() 
    { 
     // Arrange 
     var dependency = new Mock<IDependency>(); 

     var towns = new List<Town> 
     { 
      new Town { Name = "Lifford", County = "Donegal", Country="Ireland", Population = 1658 }, 
      new Town { Name = "Ballyshannon", County = "Donegal", Country="Ireland", Population = 2504 }, 
      new Town { Name = "Buxton", County = "Derbyshire", Country="United Kingdom", Population = 13599 }, 
     }; 

     var sut = new MyClass(dependency.Object); 

     // Act 
     sut.DoSomething(towns); 

     // Assert 
     // The following line needs to be fixed. 
     dependency.Verify(d => d.Execute(It.IsAny<IEnumerable<object>>(), It.IsAny<Func<object, decimal?>>())); 
    } 
} 
public interface IDependency 
{ 
    void Execute<T>(IEnumerable<T> collection, Func<T, decimal?> rateSelector); 
} 
public class MyClass 
{ 
    private readonly IDependency dependency; 
    public MyClass(IDependency dependency) 
    { 
     this.dependency = dependency; 
    } 
    public void DoSomething(IEnumerable<Town> towns) 
    { 
     var counties = towns.GroupBy(t => new {t.Country,t.County}); 
     foreach (var county in counties) 
     { 
      dependency.Execute(county, c => c.Population); 
     } 
    } 
} 
public class Town 
{ 
    public string Name { get; set; } 
    public string County { get; set; } 
    public int Population { get; set; } 
    public string Country { get; set; } 
} 

根據起訂量的測試輸出時,進行調用是:

Dependency.Execute(System.Linq.Lookup`2+Grouping[<>f__AnonymousType0`2[System.String,System.String],UniFocus.Staffscope4.Tests.Town], System.Func`2[UniFocus.Staffscope4.Tests.Town,System.Nullable`1[System.Decimal]]) 

我看到很多的MOQ(如thisthisthis)關於匿名參數的問題,但無法找到與使用匿名類型作爲實際類型參數有關的任何內容。

什麼可以放在驗證行,以便它實際上驗證內部的呼叫?

注:我的例子IDependency沒有返回值(它已經足夠複雜,我認爲),但會有答案是implictly或明確處理Setup()以及Verify()獎勵榮譽。

更新 傑西的解決方案只通過測試,因爲我在製作示例時做出了不好的選擇。我應該已經意識到任何IGrouping<out TKey, out TElement>也是IEnumerable<TElement>。有更通用的解決方案嗎?

更新2 我覺得我原來的例子可能太精細了,並沒有很好地代表我的問題的實際標題。有沒有任何解決方案可以用於這個更簡單直觀的例子?

using Moq; 
using NUnit.Framework; 

namespace Tests 
{ 
    [TestFixture] 
    public class SimpleAnonymousGenericTypeParameterTests 
    { 
     [Test] 
     public void Test() 
     { 
      // Arrange 
      var dependency = new Mock<IDependency>(); 
      var sut = new MyClass(dependency.Object); 

      // Act 
      sut.DoSomething("Donegal", "Lifford"); 

      // Assert 
      // This verify works for both calls to Execute() 
      dependency.Verify(d => d.Execute(It.IsAny<object>()), Times.Exactly(2)); 
      // This verify should specifically refer to only the first call to Execute() 
      dependency.Verify(d => d.Execute(It.IsAny</*HowToRepresentAnonymousTypeHere*/object>()), Times.Once); 
     } 
     public interface IDependency 
     { 
      void Execute<T>(T thing); 
     } 
     public class MyClass 
     { 
      private readonly IDependency dependency; 
      public MyClass(IDependency dependency) 
      { 
       this.dependency = dependency; 
      } 
      public void DoSomething(string county, string town) 
      { 
       dependency.Execute(new { county, town }); 
       object someUnknownObject = ""; 
       dependency.Execute(someUnknownObject); 
      } 
     } 
    } 
} 

回答

7

由於類型在測試環境中是已知的,因此您可以爲驗證調用提供特定的類型參數。以下更改通過測試:

dependency.Verify(d => 
    d.Execute(It.IsAny<IEnumerable<Town>>(), It.IsAny<Func<Town, decimal?>>())); 

同樣應該也適用於設置。

隨着更新2,下列通行證問候的例子,但它需要DoSomething()法的內部運作的知識,據我知道這是做的唯一方式,它的工作:

var anonymousType = new {county = "Donegal", town = "Lifford"}; 
dependency.Verify(d => d.Execute(anonymousType), Times.Once); 
+0

正試圖弄清楚如何通過。Execute()如下所示:'dependency.Execute(county,c => c.Population);'。當我把鼠標放在那裏的'縣'上時,我發現它的類型是'IGrouping <'a,Town>',其中''a是新的{string Country,string County}'。那麼Moq如何看待'Execute()'通過了'IEnumerable '?那麼我想到,一個'IGrouping '也是'IEnumerable ':'public interface IGrouping :IEnumerable ,IEnumerable'。所以你的編輯只會通過,因爲我選擇了一個不好的類型(IGrouping)作爲示例: –

+0

DoSomething將一個IEnumerable組分組以產生一個IEnumerable <分組>,然後DoSomething遍歷IEnumerable >並調用IDependency。在每個IGrouping 元素上執行。TKey看起來並不重要 - county.Key永遠不會被引用,所以匿名對象永遠不會用作分組鍵)。 IDependency.Execute 預計T將由調用者提供,在本例中將始終由DoSomething傳遞給Town。 DoSomething還有其他一些變體需要支持嗎? –

+0

你給我的見解解決了上面的例子,並解決了我在生產代碼中遇到的原始問題,所以非常感謝!不過,我覺得這是對原始問題的解決辦法,因爲我的匿名類型('IGrouping <'a,Town>')實現了非任意類型('IEnumerable '),因此很容易驗證它。在我接受這個答案之前,你能否看看我對問題的第二次更新,我的例子似乎更準確地反映了問題的標題?我開始認爲Moq沒有辦法做到這一點。謝謝你的耐心! –

1

接受的答案不適用於我,我相信這是因爲測試和有問題的對象在不同的​​程序集中,所以Moq不知道如何協調類型並且不匹配它們。

相反,我創建了下面的helper方法可以驗證所提供的匿名類型具有正確的字段和值:

public static class AnonHelpers 
{ 
    public static object MatchAnonymousType(object expected) 
    { 
     return Match.Create(Matcher(expected)); 
    } 

    private static Predicate<object> Matcher(object expected) 
    { 
     return actual => 
     { 
      var expectedProp = expected.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(expected)); 
      var actualProp = actual.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(actual)); 

      foreach (var prop in expectedProp) 
      { 
       if (!actualProp.ContainsKey(prop.Key)) 
        return false; 
       if (!prop.Value.Equals(actualProp[prop.Key])) 
        return false; 
      } 
      return true; 
     }; 
    } 
} 

他們可以像這樣使用:

var anon = new { SomeKey = "some value", SomeOtherKey = 123 }; 
myMock.Setup(x => x.MyMethod(personIDs, AnonHelpers.MatchAnonymousType(anon))).Verifiable(); 

這將創建一個匹配器,該匹配器將使用反射來匹配基於它的鍵和值的匿名類型,然後您可以使用常規驗證來查看它被調用的時間。