2014-09-24 14 views
13

Sono stato in grado di simulare DbSet dall'entità framework con Moq utilizzando questo link.How to Moq Entity Framework SqlQuery chiama

Tuttavia, vorrei ora sapere come potrei prendere in giro la chiamata a SqlQuery. Non sono sicuro se questo sia possibile o in che modo si basa sul contesto db deriso sapendo quale "query" viene chiamata.

Di seguito è quello che sto cercando di deridere.

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

Attualmente non ho provato nulla, non sapevo come iniziare a deridere questo esempio.

Il beffardo del DbSet è sotto ea ribadire, posso correttamente deridere la restituzione di un DbSet di MyObject 's, ma ora sto cercando di prendere in giro uno sqlquery che restituisce un elenco di MyObject' s.

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

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

risposta

14

Database.SqlQuery<T> non è contrassegnato come virtuale, ma Set<T>.SqlQuery è contrassegnato come virtuale.

Sulla base di documentazione Database.SqlQuery<T>

I risultati di questa ricerca non sono mai seguiti dal contesto, anche se il tipo di oggetto restituito è un tipo di entità. Utilizzare il metodo 'SqlQuery(String, Object[])' per restituire le entità monitorate dal contesto .

e Set<T>.SqlQuery documentazione

Per impostazione predefinita, le entità restituiti sono tracciati dal contesto; questo può essere modificato chiamando AsNoTracking sul DbRawSqlQuery restituito.

allora il Database.SqlQuery<T>(String, Object[]) dovrebbe essere equivalente Set<T>.SqlQuery(String, Object[]).AsNoTracking() (solo se è T entità EF, non un DTO/VM).

Quindi, se è possibile sostituire l'implementazione in:

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

si può deridere come seguire

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

Questo ha funzionato grande per me. Per me, era preferibile astrarre la logica dell'interrogazione in un aiutante, come nella risposta sopra riportata. – JamesWampler

8

Il Database proprietà e SqlQuery metodo non sono contrassegnati come virtual in modo che can't be mocked (usando Moq; si potrebbe usare un different library che può spiegare questo, ma che può essere più l'inerzia di quanto vuoi).

Avresti bisogno di utilizzare una sorta di astrazione per aggirare il problema, come ad esempio avvolgendo l'intera query del database in una classe di supporto:

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(); 
    } 
} 

Ora il metodo che si sta testando diventa:

public void YourMethod() 
{ 
    var myObjects = queryHelper.DoYourQuery("some_value"); 
} 

Quindi si iniettava il IQueryHelper nel costruttore della classe che si sta testando e si prende in giro.

Ti mancherà la copertura di prova su DoYourQuery, ma ora la query è so simple there are obviously no deficiencies.

5

È possibile aggiungere un metodo virtuale al contesto di database che è possibile ignorare in unit test :

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

Se qualcuno dovesse imbattersi in questo. Ho risolto questo con alcuni approcci. Solo un altro modo per affrontare questo.

  1. Il mio contesto è astratto tramite un'interfaccia. Ho solo bisogno di alcuni dei metodi:

    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. Tutta la mia accesso al database è attraverso metodi asincrone. Che porta a una nuova serie di problemi quando si tenta di deriderlo. Fortunatamente - è stata data risposta allo here. L'eccezione che si ottiene è relativa al simulato mancante per IDbAsyncEnumerable. Usando la soluzione fornita - l'ho appena esteso un po 'di più in modo da avere un aiuto per restituire un oggetto Mock> che ha deriso tutte le proprietà attese.

    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. Infine - utilizzando la soluzione fornita da @Yulium Chandra - mio test di SQL crudo con contesto deriso assomiglia:

    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; 
          });