2011-07-11 14 views
12

私はEF 4.1を使用してドメインモデルを構築しています。私は、検証(文字列のユーザーコード)メソッドでTaskクラスを持っており、それには私はそう、データベース内の有効なユーザーにユーザーコードマップを確認したい:Moq testing LINQ whereクエリ

public static bool Validate(string userCode) 
{ 
    IDbSet<User> users = db.Set<User>(); 
    var results = from u in users 
       where u.UserCode.Equals(userCode) 
       select u; 
    return results.FirstOrDefault() != null; 
} 

私はIDbSetには問題を模擬しないように部品番号を使用することができます。しかし、どこの呼び出しとのトラブルに遭遇した:(そのメソッドをモックその後、LINQを実行し、オブジェクトを取得するためのServiceLocatorを使用して、例えば)間接のレベルを作成するよりも他の

User user = new User { UserCode = "abc" }; 
IList<User> list = new List<User> { user }; 
var users = new Mock<IDbSet<User>>(); 
users.Setup(x => x.Where(It.IsAny<Expression<Func<User, bool>>>())).Returns(list.AsQueryable); 

Initialization method JLTi.iRIS3.Tests.TaskTest.SetUp threw exception. 
System.NotSupportedException: System.NotSupportedException: Expression 
references a method that does not belong to the mocked object: 
x => x.Where<User>(It.IsAny<Expression`1>()). 

私はどのように考えることはできませんこれをテストするには別の方法がありますが、別のレイヤーを導入する前に、方法がないことを確認したいと思います。そして、この種のLINQクエリが非常に頻繁に必要になることがわかります。そのため、サービスオブジェクトはすぐに制御不能になります。

何か魂が助けてもらえますか?ありがとう!

+1

統合テストを書くのではなく単体テストでモックを使用してこれを取り除こうとする理由はありますか? – csano

+2

ユニットEFをテストする必要はありません:) – Eranga

+0

Validate()メソッドは実際には単純化されています。それは多くの議論を持ち、各議論はテストされます。いくつかは単純な範囲チェックであり、一部はDB存在チェック(ここではユーザコードなど)です。 –

答えて

9

私が知っているように、Moqは模倣されたオブジェクト自体の仮想メソッドしか設定できませんが、あなたは拡張(静的)メソッドを設定しようとしています。これらのメソッドは、あなたのモックスコープの外にあります。

さらに、コードは間違っていてテストするのが難しいです。それをテストするには、多くの初期化が必要です。これを代わりに使用してください:

internal virtual IQueryable<User> GetUserSet() 
{ 
    return db.Set<User>(); 
} 

public bool Validate(string userCode) 
{ 
    IQueryable<User> users = GetUserSet(); 
    var results = from u in users 
        where u.UserCode.Equals(userCode) 
        select u; 
    return results.FirstOrDefault() != null; 
} 

リストを返すには、GetUserSetを設定する必要があります。 - EFはセットをからかった場合には愚かなアプローチであり、あなたがそれを行う一度なぜならあなたLINQツーオブジェクトへのLINQのツーエンティティ変更

  • あなたが実際の実装をテストされていません。このようなテストは、いくつかの主要な問題があります。これらの2つは完全に異なっており、linq-to-entitiesはlinq-to-objectsの小さなサブセットにすぎません。単位テストはlinq-to-objectsで渡すことができますが、実行時にコードは失敗します。
  • このアプローチを使用すると、DbQuery/DbSetに依存するため、Includeを使用することはできません。もう一度、それを使うための統合テストが必要です。単にオブジェクトの別の仮想メソッドとしてそれらを呼び出す -
  • これはあなたの遅延ローディングがより良いアプローチはValidate方法から、あなたのLINQクエリを削除して

動作することをテストするものではありません。あなたのValidateメソッドを疑似クエリメソッドでユニットテストし、クエリをテストするために統合テストを使用します。

+0

私は、ValidateからLINQを削除することについてあなたのポイントを得る。問題は、Validateが静的なのでGetUserSetを嘲笑することができないことです。 GetUserSetを実装するための新しいヘルパークラスを人工的に作成する必要があります。これは、間接レベルを作成することで意味しました。 –

9

IDBSetIEnumerableを実装しているので、これを試していませんが、linq文がユーザーのリストを取得するように列挙型メソッドをモックする必要があります。あなたは実際にlinqを模倣するのではなく、有効な単体テストだと思うUserCodeに基づいて適切なユーザを探しているかどうかをテストしたいコードの見た目で見てください。

var user = new User { UserCode = "abc" }; 
var list = new List<User> { user }; 
var users = new Mock<IDbSet<User>>(); 
users.Setup(x => x.GetEnumerator()).Returns(list.GetEnumerator()); 

あなたはGetEnumeratorの非ジェネリックバージョンと競合する可能性がありますが、それ、これは正しい軌道に乗ってあなたを助けるかもしれません。 次に、表示されていない他のコードに依存するデータコンテキストに模擬オブジェクトを配置する必要があります。

+0

ありがとう...私はIDbSet の完全な実装の作成を恐れていました。 –

+7

私はそれをしましたが、その模倣されたオブジェクトの '.Where'の実行は例外をスローします。 – CoderDennis

4

私はそれが簡単だけでスタブを書くことが見つかりました:

internal class FakeDbSet<T> : IDbSet<T>where T : class 
{ 
    readonly HashSet<T> _data; 
    readonly IQueryable _query; 

    public FakeDbSet() 
    { 
     _data = new HashSet<T>(); 
     _query = _data.AsQueryable(); 
    } 

    public virtual T Find(params object[] keyValues) 
    { 
     throw new NotImplementedException("Derive from FakeDbSet<T> and override Find"); 
    } 

    public T Add(T item) 
    { 
     _data.Add(item); 
     return item; 
    } 

    public T Remove(T item) 
    { 
     _data.Remove(item); 
     return item; 
    } 

    public T Attach(T item) 
    { 
     _data.Add(item); 
     return item; 
    } 

    public void Detach(T item) 
    { 
     _data.Remove(item); 
    } 

    Type IQueryable.ElementType 
    { 
     get { return _query.ElementType; } 
    } 

    Expression IQueryable.Expression 
    { 
     get { return _query.Expression; } 
    } 

    IQueryProvider IQueryable.Provider 
    { 
     get { return _query.Provider; } 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return _data.GetEnumerator(); 
    } 

    IEnumerator<T> IEnumerable<T>.GetEnumerator() 
    { 
     return _data.GetEnumerator(); 
    } 


    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T 
    { 
     return Activator.CreateInstance<TDerivedEntity>(); 
    } 

    public T Create() 
    { 
     return Activator.CreateInstance<T>(); 
    } 

    public ObservableCollection<T> Local 
    { 
     get 
     { 

      return new ObservableCollection<T>(_data); 
     } 
    } 
16

There is an article on MSDN highlighting how to mock using moq: それの要旨は、オブジェクトへのLINQをエンティティ操作にLINQを表現することです。

var mockSet = new Mock<DbSet<Blog>>(); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); 

ラディスラフが指摘するように、それは偽陽性をもたらすことができるように、オブジェクトへのLINQのエンティティへのLINQ、単に異なっているとして、これに欠点があります。しかし、MSDNの記事は、少なくとも可能であり、おそらく推奨されることを指摘しています。

この記事の元の回答から変更される可能性があることの1つは、Entity FrameworkチームがEF 6.0でエンティティフレームワークの領域を開いて、内部をより簡単に模擬できるようにしたことです。

+1

クール!私は今日これが必要でした。ちょうどそれを投稿したばかりの偶然のことです:)それは私のためにうまく動作します。 – demoncodemonkey