2015-01-25 4 views
7

Sto utilizzando un modello di repository generico Repository<TEntity> in cui i repository accedono alle entità tramite un contesto. Quindi ho un livello di servizio che accetta un contesto nel costruttore. Ora posso avere più repository in un servizio, accedendo alle entità attraverso lo stesso contesto. Abbastanza standard. Questo è perfetto per le tabelle/viste che si associano alle entità, ma non posso analizzare i dati dei test che provengono dalle stored procedure.Come eseguire il mocking e il test dell'unità Stored procedure in EF

Questa è la mia messa a punto corrente:

IDbContext:

public interface IDbContext : IDisposable 
{ 
    IDbSet<T> Set<T>() where T : class; 

    DbEntityEntry<T> Entry<T>(T entity) where T : class; 

    void SetModified(object entity); 

    int SaveChanges(); 

    // Added to be able to execute stored procedures 
    System.Data.Entity.Database Database { get; } 
} 

Contesto:

public class AppDataContext : DbContext, IDbContext 
{ 
    public AppDataContext() 
     : base("Name=CONNECTIONSTRING") 
    { 
     base.Configuration.ProxyCreationEnabled = false; 
    } 

    public new IDbSet<T> Set<T>() where T : class 
    { 
     return base.Set<T>(); 
    } 


    public void SetModified(object entity) 
    { 
     Entry(entity).State = EntityState.Modified; 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Configurations.Add(new BookingMap()); 
    } 

    // Added to be able to execute stored procedures 
    System.Data.Entity.Database Database { get { return base.Database; } } 
} 

Repository Generico:

public class Repository<T> : IRepository<T> where T : class 
{ 
    private readonly IDbContext context; 

    public Repository(IDbContext context) 
    { 
     this.context = context; 
    } 

    public IQueryable<T> GetAll() 
    { 
     return this.context.Set<T>().AsQueryable(); 
    } 

    public void Add(T entity) 
    { 
     this.context.Set<T>().Add(entity); 
    } 

    public void Delete(T entity) 
    { 
     this.context.Set<T>().Remove(entity); 
    } 

    public void DeleteAll(IEnumerable<T> entities) 
    { 
     foreach (var e in entities.ToList()) 
     { 
      this.context.Set<T>().Remove(e); 
     } 
    } 

    public void Update(T entity) 
    { 
     this.context.Set<T>().Attach(entity); 
     this.context.SetModified(entity); 
    } 

    public void SaveChanges() 
    { 
     this.context.SaveChanges(); 
    } 

    public void Dispose() 
    { 
     if (this.context != null) 
     { 
      this.context.Dispose(); 
     } 
    } 
} 

Servizio:

public class BookingService 
{ 
    IDbContext _context; 

    IRepository<Booking> _bookingRepository; 

    public BookingService(IDbContext context) 
    { 
     _context = context; 

     _bookingRepository = new Repository<Booking>(context); 
    } 

    public IEnumerable<Booking> GetAllBookingsForName(string name) 
    { 
     return (from b in _bookingRepository.GetAll() 
       where b.Name == name 
       select b); 
    } 
} 

prova:

[TestClass] 
public class BookingServiceTest 
{ 
    [TestMethod] 
    public void Test_Get_All_Bookings_For_Name() 
    { 
     var mock = new Mock<IDbContext>(); 
     mock.Setup(x => x.Set<Booking>()) 
      .Returns(new FakeDbSet<Booking> 
      { 
       new Booking { Name = "Foo" }, 
       new Booking { Name = "Bar" } 
      }); 

     BookingService _bookingService = new BookingService(mock.Object); 

     var bookings = _bookingService.GetAllBookingsForName(name); 

     Assert.AreEqual(2, bookings.Count(), "Booking count is not correct"); 
    } 
} 

Questo è perfetto per le tabelle/viste che mappano alle entità, ma non riesco a dati di test di unità che viene attraverso le stored procedure.

Ho cercato su Internet e ho trovato la proprietà DbContext.Database e sono in grado di eseguire stored procedure con la funzione .SqlQuery() e associarle a un tipo di entità.

Questo è quello che ho aggiunto alla classe Repository<T>:

public IEnumerable<T> SqlQuery(string storedProc, params object[] paramList) 
{ 
    return this.context.Database.SqlQuery<T>(storedProc, paramList); 
} 

e chiamare la funzione .SqlQuery() nella mia classe di servizio:

public IEnumerable<Booking> GetAllBookings(string name) 
{ 
    return _bookingRepository.SqlQuery("EXEC GetAllBookings @name = {0}", name); 
} 

Questa grande opera (io sono in grado di ottenere alcuni dati) , ma la mia domanda è: come posso prendere in giro e testare questo?

+0

È inutile prendere in giro una procedura memorizzata. Come faresti a sapere che la cosa reale funziona come previsto se il test unitario ha la luce verde? Se hai bisogno dei * dati * da una sproc per fare test unitari (non correlati alla logica dello sproc), puoi semplicemente creare dati fittizi. Per testare gli sproc, eseguire test di integrazione. –

+0

Esattamente, ho solo bisogno del * data * dai test unitari di sproc per fare. Come posso creare i dati falsi? – Gaui

+0

Penso che per cominciare, dovresti incapsulare sprocs nel tuo repository, Un servizio non dovrebbe sapere di SQL (o dei dettagli di persistenza). –

risposta

5

È possibile astrarre la proprietà Database con qualche interfaccia dire IDatabase con il metodo SqlQuery.

interface IDatabase 
{ 
    public IEnumerable<T> SqlQuery<T>(string sql, params Object[] parameters); 
} 

class DatabaseWrapper : IDatabase 
{ 
    private readonly Database database; 
    public DatabaseWrapper(Database database) 
    { 
     this.database = database; 
    } 

    public IEnumerable<T> SqlQuery<T>(string sql, params Object[] parameters) 
    { 
     return database.SqlQuery<T>(storedProc, paramList); 
    } 
} 

Modificare l'interfaccia IDbContext da usare al posto di IDatabase esempio concreto in modo che possiamo prendere in giro esso.

public interface IDbContext : IDisposable 
{ 
    ... 

    // Added to be able to execute stored procedures 
    IDatabase Database { get; } 
} 

e l'implementazione in questo modo

public class AppDataContext : DbContext, IDbContext 
{ 
    private readonly IDatabase database; 
    public AppDataContext() 
     : base("Name=CONNECTIONSTRING") 
    { 
     base.Configuration.ProxyCreationEnabled = false; 
     this.database = new DatabaseWrapper(base.Database); 
    } 
    ... 

    // Added to be able to execute stored procedures 
    IDatabase Database { get { return database; } } 
} 

A questo punto credo che si sa come prendere in giro il IDatabase per restituire i dati di test.

+0

Grazie per questo esempio dettagliato. Questo è un po 'quello che avevo in mente. – Gaui

+0

'System.Data.Entity.Database' non implementa' IDatabase' (o alcuna interfaccia). Non vedo come potrebbe funzionare. * Forse * 'IDbContext' potrebbe avere un metodo' SqlQuery' (che quello reale implementa come 'Database.SqlQuery'). Ma oltre a questo, penso che la tua ultima frase sia molto ottimistica. Richiederebbe parsing di istruzioni SQL, che non è banale, anche se si tratta solo di semplici chiamate sproc. È lo stesso SQL che dovrebbe essere astratto. –

+0

@GertArnold Non sono sicuro di averti capito. Non è necessario analizzare qualsiasi SQL per questo. Stiamo solo usando qualcosa come il pattern Adapter; avvolgendo 'DbContext.Database' per interfacciare qui' this.database = new DatabaseWrapper (base.Database); 'e solo delegando il lavoro a se stesso. Quindi non sono sicuro di cosa intendi. Presumo che tu abbia trascurato il mio codice o mi manchi qualcosa, –

6

Ho appena incontrato il bisogno di farlo e il mio googling mi ha portato a questa domanda. Non mi piaceva la risposta di Sriram Sakthivel, non volevo introdurre un'altra astrazione quando ne avevo già uno:

Avevo già un'interfaccia che avevo estratto dal mio DbContext e implementato in un test double.

ho semplicemente aggiunto int ExecuteSqlCommand(string sql, params object[] parameters) alla mia interfaccia, e nel contesto attuale ho implementato in questo modo:

public int ExecuteSqlCommand(string sql, params object[] parameters) 
{ 
    return Database.ExecuteSqlCommand(sql, parameters); 
} 

che ovviamente pochi delegati per l'attuale proprietà EF Database per fare il lavoro.

E nel mio test doppia ho implementato in questo modo:

public int ExecuteSqlCommand(string sql, params object[] parameters) 
{ 
    return 0; 
} 

che in realtà non fa nulla, che è il punto: Non sei unità di testare la stored procedure reale, è solo bisogno di un modo per farlo restituire qualcosa di utile.

Immagino che a un certo punto potrei aver bisogno di restituire qualcosa di diverso da 0 in un test di unità, a quel punto probabilmente introdurrò qualcosa come un Func<int> executeSqlCommandResultFactory per testare il doppio costruttore in modo che possa controllarlo, ma al momento si applica YAGNI.

Problemi correlati