2014-09-24 3 views
13

Ich konnte DbSet 's aus Entity Framework mit Moq mit dieser link.Wie Moq Entity Framework SqlQuery Aufrufe

Allerdings würde ich jetzt gerne wissen, wie ich den Aufruf von SqlQuery verspotten könnte. Nicht sicher, ob dies möglich ist oder wie es auf dem gespotteten Db-Kontext beruht, wenn man weiß, welche "Abfrage" aufgerufen wird.

Unten ist was ich versuche zu verspotten.

var myObjects = DbContext.Database 
    .SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", "some_value") 
    .ToList(); 

Ich habe noch nichts versucht, so wusste nicht, wie dieses Beispiel beginnen spöttisch.

Die Verspottung der DbSet ist unterhalb und Reiterieren wieder, ich richtig ein DbSet von MyObject Rückkehr spotten kann ‚s aber jetzt bin versucht, eine sqlquery zu verspotten, die eine Liste von MyObject gibt‘ s.

var dbContext = new Mock<MyDbContext>(); 
dbContext.Setup(m => m.MyObjects).Returns(mockObjects.Object); 

dbContext.Setup(m => m.Database.SqlQuery... something along these lines 

Antwort

14

Database.SqlQuery<T> ist nicht als virtuell markiert, aber Set<T>.SqlQuery ist als virtuell markiert.

Basierend auf Database.SqlQuery<T> Dokumentation

Die Ergebnisse dieser Abfrage werden nicht einmal durch den Kontext verfolgt, wenn der Objekttyp zurückgegeben wird, ist ein Entitätstyp. Verwenden Sie die 'SqlQuery(String, Object[])'-Methode, um Entitäten zurückzugeben, die vom -Kontext überwacht werden.

und Set<T>.SqlQuery Dokumentation

Standardmäßig werden die zurückgegebenen Einheiten durch den Kontext verfolgt werden; Dies kann geändert werden, indem AsNoTracking auf der DbRawSqlQuery zurückgegeben wird.

dann sollte die Database.SqlQuery<T>(String, Object[]) mit Set<T>.SqlQuery(String, Object[]).AsNoTracking() äquivalent sein (nur wenn T EF Entität ist, nicht eine DTO/VM).

Also, wenn Sie die Implementierung in ersetzen:

var myObjects = DbContext 
    .Set<MyObject>() 
    .SqlQuery("exec [dbo].[my_sproc] {0}", "some_value") 
    .AsNoTracking() 
    .ToList(); 

Sie können es verspotten als

var list = new[] 
{ 
    new MyObject { Property = "some_value" }, 
    new MyObject { Property = "some_value" }, 
    new MyObject { Property = "another_value" } 
}; 

var setMock = new Mock<DbSet<MyObject>>(); 
setMock.Setup(m => m.SqlQuery(It.IsAny<string>(), It.IsAny<object[]>())) 
    .Returns<string, object[]>((sql, param) => 
    { 
     // Filters by property. 
     var filteredList = param.Length == 1 
      ? list.Where(x => x.Property == param[0] as string) 
      : list; 
     var sqlQueryMock = new Mock<DbSqlQuery<MyObject>>(); 
     sqlQueryMock.Setup(m => m.AsNoTracking()) 
      .Returns(sqlQueryMock.Object); 
     sqlQueryMock.Setup(m => m.GetEnumerator()) 
      .Returns(filteredList.GetEnumerator()); 
     return sqlQueryMock.Object; 
    }); 

var contextMock = new Mock<MyDbContext>(); 
contextMock.Setup(m => m.Set<MyObject>()).Returns(setMock.Object); 
+0

Das ist für mich großartig gearbeitet. Für mich war dies besser, als die Abfragelogik in einen Helfer zu abstrahieren, wie in der obigen Antwort. – JamesWampler

8

Die Database Eigenschaft und SqlQuery Methode nicht als virtual markiert, so dass sie can't be mocked (Moq verwenden, man kann ein different library verwenden könnte, die dafür aber das Konto kann, kann mehr Trägheit sein, als Sie mögen).

Sie müssen eine Art von Abstraktion verwenden, dies zu umgehen, beispielsweise durch die gesamte Abfrage der Datenbank in einer Hilfsklasse Verpackung:

public interface IQueryHelper 
{ 
    IList<MyObject> DoYourQuery(string value); 
} 

public class QueryHelper : IQueryHelper 
{ 
    readonly MyDbContext myDbContext; 

    public QueryHelper(MyDbContext myDbContext) 
    { 
     this.myDbContext = myDbContext; 
    } 

    public IList<MyObject> DoYourQuery(string value) 
    { 
     return myDbContext.Database.SqlQuery<MyObject>("exec [dbo].[my_sproc] {0}", value).ToList(); 
    } 
} 

Nun ist die Methode, die Sie testen wird:

Dann würden Sie die IQueryHelper in den Konstruktor der Klasse, die Sie testen, injizieren und das verspotten.

Sie werden Testabdeckung auf DoYourQuery fehlen, aber jetzt ist die Abfrage so simple there are obviously no deficiencies.

5
folgen

Sie können eine virtuelle Methode, um Ihre Datenbank-Kontext hinzufügen, die Sie Unit-Tests überschreiben können :

public partial class MyDatabaseContext : DbContext 
{ 
    /// <summary> 
    /// Allows you to override queries that use the Database property 
    /// </summary> 
    public virtual List<T> SqlQueryVirtual<T>(string query) 
    { 
     return this.Database.SqlQuery<T>(query).ToList(); 
    } 
} 
1

Sollte jemand auf dieses stoßen. Ich habe das mit ein paar Ansätzen gelöst. Nur eine andere Möglichkeit, dies zu beheben.

  1. Mein Kontext wird über eine Schnittstelle abstrahiert. Ich brauche nur ein paar der Methoden:

    public interface IDatabaseContext 
    { 
        DbSet<T> Set<T>() where T : class; 
        DbEntityEntry<T> Entry<T>(T entity) where T : class; 
        int SaveChanges(); 
        Task<int> SaveChangesAsync(); 
        void AddOrUpdateEntity<TEntity>(params TEntity[] entities) where TEntity : class; 
    

    }

  2. Alle meine Datenbankzugriff ist durch asynchrones Verfahren. Das bringt eine ganze Reihe neuer Probleme mit sich, wenn man versucht, sich darüber lustig zu machen. Zum Glück - es wurde beantwortet here. Die Ausnahme, die Sie erhalten, ist mit dem fehlenden Schein für IDbAsyncEnumerable verbunden. Mit der bereitgestellten Lösung - ich habe es nur etwas erweitert, so dass ich einen Helfer hatte, der ein Mock> -Objekt zurückgab, das alle erwarteten Eigenschaften verspottete.

    public static Mock<DbSqlQuery<TEntity>> CreateDbSqlQuery<TEntity>(IList<TEntity> data) 
        where TEntity : class, new() 
    { 
        var source = data.AsQueryable(); 
        var mock = new Mock<DbSqlQuery<TEntity>>() {CallBase = true}; 
        mock.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(source.Expression); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(source.ElementType); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(source.GetEnumerator()); 
        mock.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(source.Provider)); 
        mock.As<IDbAsyncEnumerable<TEntity>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator())); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Create()).Returns(new TEntity()); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Add(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Add(i); return i; }); 
        mock.As<IDbSet<TEntity>>().Setup(m => m.Remove(It.IsAny<TEntity>())).Returns<TEntity>(i => { data.Remove(i); return i; }); 
        return mock; 
    } 
    
  3. schließlich - mit der Lösung von @Yulium Chandra zur Verfügung gestellt - meine Prüfung von rohen SQL mit verspottete Kontext wie folgt aussieht:

    public Mock<DbSet<TestModel>> MockDbSet { get; } 
    
        .... 
    
        MockDbSet.Setup(x => x.SqlQuery(It.IsAny<string>)) 
          .Returns<string,object[]> 
          ((sql, param) => 
          { 
           var sqlQueryMock = MockHelper.CreateDbSqlQuery(Models); 
    
           sqlQueryMock.Setup(x => x.AsNoTracking()) 
            .Returns(sqlQueryMock.Object); 
    
           return sqlQueryMock.Object; 
          });