0

我正在做一个工作项目,我的部分目标是学习和应用试驾开发方法。

我正在设计一个使用 EF 代码优先存储库的业务类,但我想创建一个存储库的模拟,而不是实际访问数据库。

鉴于以下存储库接口,我如何使用 MOQ 之类的模拟框架来完成此操作?挑战是我如何最小化允许您包含其他实体的 Find 方法?

public interface IRepository<T> where T : EntityBase, new()
    {
        ICollection<T> Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] fetchSelectors);
        ICollection<T> Find(Expression<Func<T, bool>> predicate, int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors);
        ICollection<T> FindAll(params Expression<Func<T, object>>[] fetchSelectors);
        ICollection<T> FindAll(int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors);
        void Save(T entity);
        void Delete(T entity);
        T Create();
    }

这是我的通用存储库的实现:

public class GenericRepository<T> : IRepository<T> where T : EntityBase, new()
    {
        public GenericRepository(IDbContext context)
        {
            Guard.ArgumentNotNull(context, "context");
            this.Context = context;
            this.DbSet = this.Context.CreateDbSet<T>();
        }

        protected IDbContext Context
        {
            get;
            set;
        }

        protected System.Data.Entity.IDbSet<T> DbSet
        {
            get;
            set;
        }

        public virtual ICollection<T> Find(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] fetchSelectors)
        {
            return this.BuildQuery(predicate, fetchSelectors).ToList();
        }

        public virtual ICollection<T> FindAll(params Expression<Func<T, object>>[] fetchSelectors)
        {
            return this.BuildQuery(p => true, fetchSelectors).ToList();
        }

        public virtual void Save(T entity)
        {
            Guard.ArgumentNotNull(entity, "entity");
            this.Context.SaveOrUpdate(entity);
        }

        public virtual void Delete(T entity)
        {
            Guard.ArgumentNotNull(entity, "entity");
            this.DbSet.Remove(entity);
        }

        public T Create()
        {
            return this.DbSet.Create();
        }

        private IQueryable<T> BuildQuery(Expression <Func<T, bool>> predicate, params Expression<Func<T, object>>[] fetchSelectors)
        {
            var query = this.DbSet as IQueryable<T>;
            if (fetchSelectors != null)
            {
                foreach (var fetchSelector in fetchSelectors)
                {
                    query = query.Include(fetchSelector);
                }
            }
            return query.Where(predicate);
        }


        public ICollection<T> Find(Expression<Func<T, bool>> predicate, int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors)
        {
            count = (this.DbSet as IQueryable<T>).Count(predicate);

            if (size < 1 || size > count)
            {
                throw new ArgumentOutOfRangeException("size");
            }

            var maxPageNumber = (count + size - 1) / size;
            if (pageNumber < 1 || pageNumber > maxPageNumber)
            {
                throw new ArgumentOutOfRangeException("pageNumber");
            }
            if (sortOrder != "asc" && sortOrder != "desc")
            {
                throw new ArgumentException("sortOrder");
            }
            var skipCount = (pageNumber - 1) * size;
            var query = BuildQuery(predicate, fetchSelectors);
            query = sortOrder == "asc" ? query.OrderBy(orderBy) : query.OrderByDescending(orderBy);
            return query.Skip(skipCount).Take(size).ToList();
        }

        public ICollection<T> FindAll(int pageNumber, int size, Expression<Func<T, object>> orderBy, string sortOrder, out int count, params Expression<Func<T, object>>[] fetchSelectors)
        {
            return Find(p => true, pageNumber, size, orderBy, sortOrder, out count, fetchSelectors);
        }
    }

最后,这里是 IDbContext

public interface IDbContext
    {
        void SaveOrUpdate<T>(T entity) where T : EntityBase;
        IDbSet<TEntity> CreateDbSet<TEntity>() where TEntity: EntityBase;
    }
4

1 回答 1

1

因此,假设您有一个业务逻辑类,它依赖于IRepository<T>

public class FooBusinessLogicClass
{
    readonly IRepository<Foo> repository;

    public FooBusinessLogicClass( IRepository<Foo> repository )
    {
        this.repository = repository;
    }

    public ICollection<Foo> FindFoo()
    {
        ...
        var collection = repository.Find( x => x.SomeLambdaExpression(), y => y.SomeOtherExpression() );
        ...

        return collection;
    }
}

如果您只是希望您的存储库在调用它时返回一些假数据并且您不关心传递给它的参数,您可以使用 Moq 函数It.IsAny<T>()

    public void Verify()
    {
        //Arrange
        var repositoryMock = new Mock<IRepository<Foo>>();
        var example = new FooBusinessLogicClass( repositoryMock.Object );

        //Our fake data for the repository to return
        var expectedResult = new[] { new Foo(), new Foo() };

        //Our setup that ignores the lambda expressions
        repositoryMock.Setup( mock => mock.Find(
                    It.IsAny<Expression<Func<Foo, bool>>>(),
                    It.IsAny<Expression<Func<Foo, object>>[]>() ) )
                .Returns( expectedResult );

        //Act
        var actualResult = example.FindFoo();

        //Assert
        Assert.AreEqual( expectedResult, actualResult );
    }
}

如果您确实关心参数是什么(假设您想验证是否Find已调用),则只需将 lambda 表达式包含在您的Verify():

        //Assert
        repositoryMock.Verify( mock => mock.Find( 
                x => x.SomeLambdaExpression(), 
                y => y.SomeOtherExpression() ) );

无论如何,实现GenericRepository<T>和接口的细节IDBContext与我们业务逻辑类的测试无关。现在,您可能想针对您的实现编写单元测试GenericRepository<T>(使用 的模拟IDBContext),但这是单独的问题。

于 2012-06-06T20:06:10.010 回答