2016-02-15 10 views
6

頭を包むのに面白い問題があります。Moqを使って `object.Equals(object obj)`をモックする方法

public interface IMyThing 
{ 
    int Id { get; } 
} 

ここで、このインターフェイスを使用するコードをテストしたいと思います。たぶんLINQの魔法のあるもの。このような何か:

public class SomeClass 
{ 
    private IMyThing _thing; 

    ... 

    public bool HasThing(IEnumerable<IMyThing> things) 
    { 
     return things.Contains(_thing); 
    } 
} 

私はMoqを使用してIMyThingを実装するすべてのオブジェクトをからかっている:ここで

public static IMyThing MockMyThing(int newId) 
{ 
    var mock = new Mock<IMyThing>(); 
    mock.Setup(s => s.Id).Returns(newId); 
    mock.Setup(s => s.Equals(It.IsAny<object>())).Returns<object>(obj => 
    { 
     if (typeof(IMyThing).IsAssignableFrom(obj.GetType())) 
     { 
      return ((IMyThing)obj).Id == newId; 
     } 

     return false; 
    }); 

    return mock.Object; 
} 

事です。上記のコードは警告なしでコンパイルされますが、決して動作しません。 Moqは、Equals()メソッドのインターセプタを作成しますが、決して到達しません。代わりに、オブジェクトプロキシのequalsメソッドが呼び出されます。私はインターフェイスを嘲笑しているが、具体的なクラスではないという事実を非難している。

更新:ちょうど実現したMoqは、インターセプタも作成しません。

もちろん私はこのようなIMyThingインタフェースを拡張することができます:

public interface IMyThing : IEquatable<IMyThing> 
{ 
    int Id { get; } 
} 

LINQ演算子はIEquatable<T>インターフェイスを認識し、それを使用します。

ので、私はこれを行うにはしたくない

:私は私のモデルを汚染チェックしたくない

  • これは
  • IEquatable<T>は、この目的の
  • のためのものではなかった他のIMyThingオブジェクトでのみ使用可能ですそれを嘲笑させるだけです

あなたはこれをどのように解決しますか?

+0

'HasThing()'メソッド中にいくつかの複雑なシナリオをテストしようとしているなら、おそらく 'IEnumerable 'を受け入れるべきです。 「間接的なレベルのインダイレクション」を導入してみませんか? –

答えて

1

GithubのMoqプロジェクト(issue #248を参照)にコードの寄稿を行った。この変更により、インタフェースモックの場合でも、object.Equals(object obj)object.GetHashCode()object.ToString()をモックすることが可能です。

受け入れられるかどうかを確認します。

+1

投稿が受け入れられました!やめ! – koloman

0

多分私はあなたを完全に理解していませんが、私は伸びや上書きを見ませんEquals()IMyThingの機能ですか? 私は最初のアプローチが上書きされるか、または機能を拡張すると思います。 もし私がIMyThing : IEquatable<IMyThing>の仕事をしなければ、IEquatable<T>はこの目的のためではありませんでしたが、それは閉じることがわかります。 と最後と私はちょうど私が問題はEquals方法は、あなたが嘲笑されているインターフェイスではないという事実から来ていると思いますbool IsEquals(IMyThing other){//check if equals}

+1

'IMyThing'の具体的な実装がないので、' Equals() 'の具体的なオーバーライドはありません。 ** IMyThingの実装があります**私はふりをするためにモックフレームワーク 'Moq'を使っています**。したがって、単純に 'Equals()'をオーバーライドすることはできません。いくつかの 'IsEqual()'メソッドを実装するのと同じ理由で、どちらも動作しません。 – koloman

1

のような関数を作成し、あなたがそれを行う必要がありますが、それが存在していることと思ういけません。オーバーライド可能なEqualsメソッドを持つクラスを作成した場合(何もしなくても)、それをモックできます。

public class MyTestThing : IMyThing 
{ 
    public virtual int Id { get; } 

    public override bool Equals(object obj) 
    { 
     return base.Equals(obj); 
    } 
} 

[TestCase(55)] 
public void Can_mock_equals_method(int newId) 
{ 
    var mockThing = new Mock<MyTestThing>(); 

    mockThing.Setup(t => t.Id).Returns(newId); 
    mockThing.Setup(t => t.Equals(It.IsAny<object>())) 
     .Returns<object>(t => (t as IMyThing)?.Id == newId); 

    Assert.That(mockThing.Object.Equals(new MyRealThing(newId))); 
} 

あなたはMyTestThingEquals方法をコメントアウトした場合部品番号は、もはやそれを模擬することはできませんので、このテストは、失敗することに注意してください。

このようなテストクラスを作成する場合は、実際にEqualsをクラス自体に完全に実装すると、Moqで設定する必要はありません。さらに一歩進んで、実装された唯一のメソッドがEqualsである抽象基底クラスを作成し、この両方をMoqでのテストで使用し、そこから実際の実装を導き出すことができます。

+0

これはうまくいくでしょう。 'IMyThing'が数多くのメンバーと多数のアセンブリで多数の実装を持っているとすぐにそれは厄介です。インターフェイスの振る舞いを模擬するだけで、はるかに便利になります。 – koloman

1

比較を実行する方法を変更できる場合は、直接比較は使用しないでください。比較を行うことができるオブジェクトを使用してください。

は、このようなあなたのクラスへの変更を行う考えてみましょう:

public class SomeClass 
{ 
    private IMyThing _thing; 

    public bool HasThing(IEnumerable<IMyThing> things, IEqualityComparer<IMyThing> comparer = null) 
    { 
     comparer = comparer ?? EqualityComparer<IMyThing>.Default; 
     return things.Contains(_thing, comparer); 
    } 
} 

次に、あなただけのようにも比較子を模擬しなければなりません。

+0

それは本当に面白い解決策です!しかし、HasThing(...)の呼び出し側は必ずしも 'IMyThing'自体の実装を知っているとは限りません。したがって、どの等価比較子を使うのかは当然知りません。したがって、これは私のテストの問題を解決しますが、実際のコードは必要以上に複雑になります。最終的に、具体的なIMyThingの実装の比較は、まったく変更の対象にはなりません。同意しますか? – koloman

関連する問題