2016-11-14 6 views
0

Ich baue Komponententests für eine kleine App, die wir erstellen müssen.Mocking Kontext und Repository mit UnitOfWork

Ich habe das Repository/Unit Of Work-Muster implementiert. Meine Manager-Klassen implementieren das Muster der Arbeitseinheit.

Für eine gegebene Schnittstelle:

public interface IUserManager 
{ 
    List<ApplicationUser> GetUsers(Expression<Func<ApplicationUser, bool>> filter = null); 
    ApplicationUser GetUser(Expression<Func<ApplicationUser, bool>> filter); 
    ApplicationUser AddUser(string username, List<string> environmentIds, bool isAdmin = false); 
    void DeleteUser(string username); 
    ApplicationUser UpdateUser(string id, List<string> environmentIds, bool isAdmin = false); 
    IList<string> GetUserRoles(string id); 
} 

ich umgesetzt haben

public class UserManager : IUserManager 
{ 

    #region private fields 

    private readonly IRepository<ApplicationUser> _userRepository; 
    private readonly IRepository<Application> _applicationRepository; 
    private readonly IRepository<Role> _roleRepository; 
    private readonly IActiveDirectoryManager _activeDirectoryManager; 


    #endregion 

    #region ctor 

    public UserManager(AppDbContext context, IActiveDirectoryManager activeDirectoryManager) 

    { 
     _activeDirectoryManager = activeDirectoryManager; 
     _userRepository = new Repository<ApplicationUser>(context); 
     _applicationRepository = new Repository<Application>(context); 
     _roleRepository = new Repository<Role>(context); 
    } 

    #endregion 


    #region IUserManager 

    public ApplicationUser AddUser(string username, List<string> applicationIds, bool isAdmin = false) 
    { 
     //Get the environments in the list of environmentIds 
     var applications = _applicationRepository.Get(e => applicationIds.Contains(e.Id)).ToList(); 

     //Get the user from AD 
     var user = _activeDirectoryManager.GetUser(username); 

     //set the Id 
     user.Id = Guid.NewGuid().ToString(); 

     //add the environments to the user 
     applications.ForEach(x => 
     { 
      user.Applications.Add(x); 
     }); 

     //if the user is an admin - retrieve the role and add it to the user 
     if (isAdmin) 
     { 
      var role = _roleRepository.Get(r => r.Name == "admin").FirstOrDefault(); 
      if (role != null) 
      { 
       user.Roles.Add(role); 
      } 
     } 

     //insert and save 
     _userRepository.Insert(user); 
     _userRepository.Save(); 

     //return the user 
     return user; 

    } 

//removed for brevity 
} 

My Unit-Test-Klasse:

[TestClass] 
public class UserManagerUnitTest 
{ 
    private readonly Mock<IActiveDirectoryManager> _adManager; 
    private readonly IUserManager _userManager; 
    private readonly Mock<IRepository<Application>> _applicationRepository; 
    private readonly Mock<IRepository<ApplicationUser>> _userRepository; 
    private readonly Mock<IRepository<Role>> _roleRepository; 


    public UserManagerUnitTest() 
    { 
     var context = new Mock<AppDbContext>(); 
     _adManager = new Mock<IActiveDirectoryManager>(); 

     _applicationRepository = new Mock<IRepository<Application>>(); 
     _userRepository = new Mock<IRepository<ApplicationUser>>(); 
     _roleRepository = new Mock<IRepository<Role>>(); 

     _userManager = new UserManager(context.Object, _adManager.Object); 

    } 

    [TestMethod] 
    [TestCategory("AddUser"), TestCategory("Unit")] 
    public void AddUser_ValidNonAdmin_UserIsAdded() 
    { 
     #region Arrange 

     string username = "testUser"; 
     List<string> applicationIds = new List<string>() {"1", "2", "3"}; 

     _applicationRepository.Setup(x => x.Get(It.IsAny<Expression<Func<Application, bool>>>(), 
      It.IsAny<Func<IQueryable<Application>, IOrderedQueryable<Application>>>(), It.IsAny<string>())) 
      .Returns(new List<Application>()); 

     _adManager.Setup(x => x.GetUser(It.IsAny<string>())).Returns(new ApplicationUser()); 


     #endregion 

     #region Act 

     var result = _userManager.AddUser(username, applicationIds, false); 

     #endregion 

     #region Assert 
     Assert.IsNotNull(result); 
     Assert.IsFalse(result.IsAdmin); 
     #endregion 
    } 

} 

Und schließlich die Repository-Schnittstelle:

public interface IRepository<TEntity> where TEntity : class 
{ 
    IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, 
     Func<IQueryable<TEntity> , IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = ""); 

    TEntity GetById(object id); 
    void Insert(TEntity entity); 
    void Delete(object id); 
    void Delete(TEntity entityToDelete); 
    void Update(TEntity entityToUpdate); 
    void Save(); 

} 

und Umsetzung:

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class 
{ 
    private readonly AppDbContext _context; 
    internal DbSet<TEntity> DbSet; 

    public Repository(AppDbContext context) 
    { 
     _context = context; 
     DbSet = _context.Set<TEntity>(); 
    } 

    public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, 
     Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "") 
    { 
     IQueryable<TEntity> query = DbSet; 

     if (filter != null) 
      query = query.Where(filter); 

     foreach (var prop in includeProperties.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries)) 
     { 
      query = query.Include(prop); 
     } 

     if (orderBy != null) 
     { 
      return orderBy(query).ToList(); 
     } 
     else 
     { 
      return query.ToList(); 
     } 


    } 

    public virtual TEntity GetById(object id) 
    { 
     return DbSet.Find(id); 
    } 

    public virtual void Insert(TEntity entity) 
    { 
     DbSet.Add(entity); 
    } 

    public virtual void Delete(object id) 
    { 
     TEntity entityToDelete = DbSet.Find(id); 
     Delete(entityToDelete); 
    } 

    public void Get(Expression<Func<Application, bool>> expression, Func<IQueryable<Application>> func, IOrderedQueryable<Application> orderedQueryable) 
    { 
     throw new NotImplementedException(); 
    } 

    public virtual void Delete(TEntity entityToDelete) 
    { 
     if (_context.Entry(entityToDelete).State == EntityState.Detached) 
     { 
      DbSet.Attach(entityToDelete); 
     } 
     DbSet.Remove(entityToDelete); 
    } 

    public virtual void Update(TEntity entityToUpdate) 
    { 
     DbSet.Attach(entityToUpdate); 
     _context.Entry(entityToUpdate).State = EntityState.Modified; 
    } 

    public void Save() 
    { 
     _context.SaveChanges(); 
    } 
} 

mein Problem ist in der Mock-IRepository<Application>

  _applicationRepository.Setup(x => x.Get(It.IsAny<Expression<Func<Application, bool>>>(), 
      It.IsAny<Func<IQueryable<Application>, IOrderedQueryable<Application>>>(), It.IsAny<string>())) 
      .Returns(new List<Application>()); 

Aus irgendeinem Grund - tatsächliche Methode im Vergleich zu dem außer Kraft gesetzten Proxy von Moq verwendet wird. Wenn der Test ausführt - Ich erhalte eine Nullreferenz auf der Get-Methode des Endlagers - speziell auf den query = DbSet:

public Repository(AppDbContext context) 
    { 
     _context = context; 
     DbSet = _context.Set<TEntity>(); 
    } 

    public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, 
     Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "") 
    { 
     IQueryable<TEntity> query = DbSet; **//null here because db should be** mocked 

     if (filter != null) 
      query = query.Where(filter); 

ich nur die Usermanager Implementierung zu testen, versuchen - nicht die Repository-Implementierung.

Was wäre der richtige Weg, um diesen Test einzurichten?

Antwort

3

Das Problem besteht darin, dass Sie den AppDbContext im Konstruktor von UserManager übergeben, wodurch er davon abhängig ist. Die Klasse wiederum schafft interne Instanzen der Repositories, so immer die konkreten Klassen:

public UserManager(AppDbContext context, IActiveDirectoryManager activeDirectoryManager) 
{ 
    _activeDirectoryManager = activeDirectoryManager; 
    _userRepository = new Repository<ApplicationUser>(context); 
    _applicationRepository = new Repository<Application>(context); 
    _roleRepository = new Repository<Role>(context); 
} 

Sie sollten stattdessen abstrahieren die Schaffung der Repositories und den Konstruktor so ändern, dass es eine Instanz nimmt basierend auf die Schnittstellen:

public UserManager(IRepository<ApplicationUser> userRepository, IRepository<Application> applicationRepository, IRepository<Role> roleRepository, IActiveDirectoryManager activeDirectoryManager) 
{ 
    _activeDirectoryManager = activeDirectoryManager; 
    _userRepository = userRepository; 
    _applicationRepository = applicationRepository; 
    _roleRepository = roleRepository; 
} 

auf diese Weise können aus den Repositories zu abstrahieren können so Ihre Mocks anstelle der realen Klassen verwendet werden.

+0

So hatte ich es ursprünglich. Ich habe jedoch ein Problem beim Speichern festgestellt, da die Repositorys alle verschiedene Instanzen des Kontexts hatten. Ich benutze Autofac, also habe ich die UserManager-Registrierung geändert, um jedes Repository Instance mit der gleichen Instanz meines db-Kontextes aufzubauen. Aufgeräumt. Vielen Dank. – JDBennett

+0

Nun, wenn das der Fall ist, sollten Sie einen Schritt zurückgehen und einen anderen Blick auf Ihr Design werfen. Repositories sollten keine Abhängigkeiten untereinander haben. Wenn Probleme beim Speichern auftreten, bedeutet dies wahrscheinlich, dass Sie Überschneidungen haben, die umstrukturiert werden sollten. – JuanR

Verwandte Themen