Mocking Context and Repository with UnitOfWork

半城伤御伤魂 提交于 2019-12-24 09:29:46

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!