问题
I am building out unit tests for a small app we need to build.
I have implemented the Repository / Unit Of Work pattern. My manager classes implement the unit of work pattern.
For a given interface:
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);
}
I have implemented
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 class:
[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
}
}
And finally the repository interface:
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();
}
And implementation:
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();
}
}
my problem is in the 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>());
For some reason - actual method is being used versus the overridden proxy from Moq. When the test executes - I get a null reference on the Get method of the repository - specifically on the 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);
I am trying to test just the UserManager implementation - not the repository implementation.
What would be the correct way to set this test up?
回答1:
The issue is you are passing the AppDbContext in the constructor of UserManager, which makes it dependent on it. The class in turn is creating internal instances of the repositories, thus always using the concrete classes:
public UserManager(AppDbContext context, IActiveDirectoryManager activeDirectoryManager)
{
_activeDirectoryManager = activeDirectoryManager;
_userRepository = new Repository<ApplicationUser>(context);
_applicationRepository = new Repository<Application>(context);
_roleRepository = new Repository<Role>(context);
}
You should instead abstract out the creation of the repositories and modify the constructor so that it takes an instance based on the interfaces:
public UserManager(IRepository<ApplicationUser> userRepository, IRepository<Application> applicationRepository, IRepository<Role> roleRepository, IActiveDirectoryManager activeDirectoryManager)
{
_activeDirectoryManager = activeDirectoryManager;
_userRepository = userRepository;
_applicationRepository = applicationRepository;
_roleRepository = roleRepository;
}
This way you are able to abstract out the repositories so your mocks are used instead of the real classes.
来源:https://stackoverflow.com/questions/40598379/mocking-context-and-repository-with-unitofwork