2015-08-20 5 views
5

Ho un progetto ASP.NET MVC, e sto scrivendo il mio servizio qualcosa sulla falsariga di questo:ASP.NET Testing DbContext a metodi locali

public class UserService : IUserService 
{ 
    public void AddUser(UserModel userModel) 
    { 
     if (ModelState.IsValid){ 
      var user = new User 
      { 
       FirstName = userModel.FirstName, 
       LastName = userModel.LastName, 
       Email = userModel.Email, 
       Age = userModel.Age 
      }; 
      using (var context = new MyDbContext()) 
      { 
       context.Users.Add(user); 
      } 
     } 
    } 
    //... More code for service 
} 

E la mia classe DbContext:

public class MyDbContext : DbContext 
{ 
    public MyDbContext(): base("name=mydb"){} 

    public DbSet<User> Users {get; set;} 

    //.. More domain objects. 
} 

Questo funziona bene per la maggior parte, ma il problema sorge quando voglio provare e testarlo. Sto seguendo le linee guida generali here con derisione, ma solo se DbContext è dichiarato come istanza di classe, al contrario di una variabile funzionale. Voglio farlo in questo modo perché mi consente di avere il controllo sulla transazione e sullo smaltimento del contesto. Qualcuno ha qualche suggerimento su come farlo?

mie schernisce sono come questo:

public class UserServiceTest 
{ 
    [Fact] 
    public void TestAddUser() 
    { 
     var userSet = GetQueryableMockDbSet(userData.ToArray()); 
     var context = new MyDbContext 
     { 
      Users = userSet 
     }; 
     var userService = new UserService(); 
     userService.AddUser(); // HOW DO I GET CONTEXT IN HERE? 
    } 

    private static DbSet<T> GetQueryableMockDbSet<T>(params T[] sourceList) where T : class 
     { 
      var queryable = sourceList.AsQueryable(); 

      var dbSet = new Mock<DbSet<T>>(); 
      dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider); 
      dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression); 
      dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType); 
      dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator()); 

      return dbSet.Object; 
     } 
} 
+0

È necessario passare la vostra 'DbContext' al costruttore del servizio. Leggi su Dependency Injection e usa un contenitore IoC per semplificare le cose –

+0

Non può passare l'istanza al contenitore, poiché ha affermato che vuole controllare la creazione dell'istanza all'interno del metodo. Stavo pensando che OP possa iniettare un 'Factory' nel servizio che creerà l'istanza, e quindi prendere in giro il metodo di creazione Factory con l'oggetto fittizio – 3dd

+0

Il metodo factory sembra promettente: sarebbe qualcosa come FactoryContext.Create() restituisce un Oggetto MyDbContext? Il metodo Create è statico, ovviamente. –

risposta

2

Un modo per raggiungere il tuo obiettivo di

voglio farlo in questo modo perché mi permette di avere il controllo sulla disposizione delle transazioni e il contesto.

sarebbe quella di fare qualcosa sulla falsariga di

public class UserService : IUserService { 
    private ContextFactory _ContextFactory; 

    public UserService(ContextFactory contextFactory) { 
     _ContextFactory = contextFactory; 
    } 

    public void AddUser(UserModel userModel) { 
     ... 
     using (var context = _ContextFactory.CreateInstance()) { 
      context.Users.Add(user); 
     } 
    } 

}

Ciò consentirebbe di iniettare diverse istanze di ContextFactory che provocherebbero la DbContext utilizzando la Falsa

0

è necessario passare la DbContext nel costruttore del servizio. Il modo più semplice per farlo è iniziare a utilizzare qualsiasi framework IoC (AutoFac, Unity, Ninject ecc.). Per simulare il database il modo più semplice è quello di iniziare a utilizzare qualsiasi libreria di derisione come Moq e RhinoMocks.

public class UserService : IUserService 
{ 
    private MyDbContext _context; 

    public UserService (MyDbContext dbContext) 
    { 
     _context = dbContext; 
    } 
} 
+0

Come accennato sopra, sono a conoscenza di questo metodo, ma preferirei non perché voglio avere il controllo delle mie transazioni qui, e gestire lo smaltimento del contesto . –

+0

Tuttavia è necessario passare il contesto al servizio. O nel costruttore o nel metodo.Se lo fai con un framework IoC, gestirà lo smaltimento per te in maniera positiva. – Rikard

+0

E quando si scrive tesst la classe di test stessa dovrebbe "possedere" il dbContext (deriso o meno) non la classe sotto test. – Rikard

1

I preferirei sicuramente l'approccio IOC ma se vuoi una soluzione rapida senza riscrivere troppo codice puoi fare qualcosa del genere:

public class UserService : IUserService 
{ 
    public static Func<MyDbContext> CreateContext =() => new MyDbContext(); 

    public UserService() 
    { 
    } 

    public void AddUser(UserModel userModel) 
    { 
     ... 
     using (var context = CreateContext()) 
     { 
      context.Users.Add(user); 
     } 
    } 
} 

Non c'è bisogno di cambiare il vostro codice di produzione ma nel tuo TestMethod si può fare

var context = new MyDbContext 
    { 
     Users = userSet 
    }; 
    UserService.CreateContext =() => context; 
+0

Sebbene tecnicamente non segua le best practice, che è quello che stavo cercando di fare, amo ancora molto questa risposta. Semplice e sfacciato. –

+0

Sì, se già segui i pattern IOC iniettando una nuova dipendenza in una classe è un gioco da ragazzi, ma se devi modificare 50 costruttori nel tuo codice questa è una buona soluzione. Credo che questo sia un modello comune per le librerie di terze parti (penso di averlo visto in quartz.net o nuget.core). Mantiene il codice simpel, non ha bisogno di dipendenza da un container IOC ma stil mantiene la flessibilità di iniettare oggetti. –