2015-11-10 10 views
5

Sto usando moq, ef 6 e xunit. Mi trovo a scrivere questo codice ancora e ancora e ho pensato che forse avrei potuto trasformarlo in un metodo generico, ma avendo qualche problema.Come scrivere questo codice di setup EF Mock come un Boilerplate generico riutilizzabile?

public static void CreateSalesMock(List<Sale> sales, Mock<DatabaseContext> dbContextMock) 
{ 
    var data = sales.AsQueryable(); 

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

    dbContextMock.Setup(x => x.Sales).Returns(mockSet.Object); 
} 

Ora ho molti altri tavoli nel mio database così che se ho potuto scrivere un metodo che avrebbe preso in un elenco di tali dati e impostarlo in modo che può deridere interrogazione attraverso di esso sarebbe grande.

public static void CreateMockSet<T, TA, TB>(T dataList, TA model, 
    Func<TB> lambda, Mock<DatabaseContext> dbContextMock) 
    where T : List<T> 
    where TA: Mock<DbSet<TA>> 
{ 
    var data = dataList.AsQueryable(); 

    model.As<IQueryable<T>>() 
     .Setup(x => x.Provider) 
     .Returns(data.Provider); 
    model.As<IQueryable<T>>() 
     .Setup(x => x.Expression) 
     .Returns(data.Expression); 
    model.As<IQueryable<T>>() 
     .Setup(x => x.ElementType) 
     .Returns(data.ElementType); 
    model.As<IQueryable<T>>() 
     .Setup(x => x.GetEnumerator()) 
     .Returns(data.GetEnumerator()); 

    dbContextMock.Setup(x => lambda); 
} 

Finora ce l'ho, ma non sono sicuro che funzionerà o meno. Sono bloccato passando nella parte "lambda" (x => x.Sales per esempio) quindi non riesco nemmeno a provarlo.

+0

Penso che si desidera modificare il 'WHERE' vincolo di introdurre un'interfaccia:' dove T: Lista '. –

+0

Consiglierei uno sguardo al Pattern Builder vedere https://www.kenneth-truyers.net/2013/07/15/flexible-and-express-unit-tests-with-the-builder-pattern/ –

+0

@ ToddSprang Non sono sicuro di cosa sarebbe IBase. Potresti dare più di un esempio – chobo2

risposta

9

Tim Larson già offerto una grande soluzione per questo codice boilerplate in his blog:

public static class DbSetMocking 
{ 
    private static Mock<DbSet<T>> CreateMockSet<T>(IQueryable<T> data) 
      where T : class 
    { 
     var queryableData = data.AsQueryable(); 
     var mockSet = new Mock<DbSet<T>>(); 
     mockSet.As<IQueryable<T>>().Setup(m => m.Provider) 
       .Returns(queryableData.Provider); 
     mockSet.As<IQueryable<T>>().Setup(m => m.Expression) 
       .Returns(queryableData.Expression); 
     mockSet.As<IQueryable<T>>().Setup(m => m.ElementType) 
       .Returns(queryableData.ElementType); 
     mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()) 
       .Returns(queryableData.GetEnumerator()); 
     return mockSet; 
    } 

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
      this IReturns<TContext, DbSet<TEntity>> setup, 
      TEntity[] entities) 
     where TEntity : class 
     where TContext : DbContext 
    { 
     Mock<DbSet<TEntity>> mockSet; 
     return ReturnsDbSet(setup, entities, out mockSet); 
    } 

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
      this IReturns<TContext, DbSet<TEntity>> setup, 
      IQueryable<TEntity> entities) 
     where TEntity : class 
     where TContext : DbContext 
    { 

     Mock<DbSet<TEntity>> mockSet; 
     return ReturnsDbSet(setup, entities, out mockSet); 
    } 

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
      this IReturns<TContext, DbSet<TEntity>> setup, 
      IEnumerable<TEntity> entities) 
     where TEntity : class 
     where TContext : DbContext 
    { 
     Mock<DbSet<TEntity>> mockSet; 
     return ReturnsDbSet(setup, entities, out mockSet); 
    } 

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
    this IReturns<TContext, DbSet<TEntity>> setup, 
    TEntity[] entities, out Mock<DbSet<TEntity>> mockSet) 
     where TEntity : class 
     where TContext : DbContext 
    { 
     mockSet = CreateMockSet(entities.AsQueryable()); 
     return setup.Returns(mockSet.Object); 
    } 

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
      this IReturns<TContext, DbSet<TEntity>> setup, 
      IQueryable<TEntity> entities, out Mock<DbSet<TEntity>> mockSet) 
     where TEntity : class 
     where TContext : DbContext 
    { 

     mockSet = CreateMockSet(entities); 
     return setup.Returns(mockSet.Object); 
    } 

    public static IReturnsResult<TContext> ReturnsDbSet<TEntity, TContext>(
    this IReturns<TContext, DbSet<TEntity>> setup, 
    IEnumerable<TEntity> entities, out Mock<DbSet<TEntity>> mockSet) 
     where TEntity : class 
     where TContext : DbContext 
    { 
     mockSet = CreateMockSet(entities.AsQueryable()); 
     return setup.Returns(mockSet.Object); 
    } 

} 

Poi nel UT lo si utilizza come il seguente:

var context = new Mock<DatabaseContext>(); 
context.setup(x => x.Sales).ReturnsDbSet(new List<Sale>(){put here the items..}); 

Modifica

Ho aggiornato il codice. Ora ci sono altri 3 sovraccarichi che consente la verifica sul DbSet<T> proprietà:

[TestMethod] 
    public void TestMethod1() 
    { 
     var sales = new List<Sale> 
     { 
      new Sale() {id = 1}, 
      new Sale() {id = 6}, 
      new Sale() {id = 5}, 
      new Sale() {id = 4}, 
      new Sale() {id = 3}, 
      new Sale() {id = 2} 
     }; 
     var fakeContest = new Mock<SalesContext>(); 
     Mock<DbSet<Sale>> fakeSet; 
     fakeContest.Setup(context => context.Sales).ReturnsDbSet(sales, out fakeSet); 

     var itemsToRemove = sales.Where(sale => sale.id%2 == 0); 


     fakeContest.Object.Sales.RemoveRange(itemsToRemove); 


     fakeSet.Verify(set => set.RemoveRange(itemsToRemove)); 

    } 
+0

Cool, questo è quello che stavo cercando, ma cosa succede se ho bisogno di aggiungere più setup a Mockset? Per esempio voglio vedere se RemoveRange è stato chiamato così prima ho appena eseguito la verifica su SalesMock e questo ha funzionato. ma ora non ce l'ho, non sono sicuro di cosa fare. Ho provato a fare la verifica tramite dbContext.Verify (x => x.Sales.RemoveRange (...)) ma questo fallisce sempre. – chobo2

+0

@ chobo2 Non fare una cosa del genere. 'context.Sales' è un' Elenco' di 'Vendite' che è una raccolta BCL. Non dovresti prendere in giro una raccolta BCL. Invece di farlo, verifica che l'elenco non contenga più gli elementi. –

+0

Come posso verificare che l'elenco non contenga più gli elementi? L'ho provato prima, ma il mio reparto test non è mai sembrato averli rimossi. Come nel mio servizio, ho visto che venivano rimossi, ma quando guardavo la mia collezione originale, erano lì dove tutti erano ancora lì. – chobo2