【问题标题】:How to Moq a service in a controller which use unitofwork with generic repository如何在使用 unitofwork 和通用存储库的控制器中最小化服务
【发布时间】:2012-06-01 18:32:12
【问题描述】:

我是 TDD(Asp.net MVC3 环境)的新手,并试图采用 TDD 作为我们更好的开发方法。

在我们的生产代码中,我们有以下场景

在网络中

//Autofac used to resolve Dependency
TestController(XService xSerivice,YSerivice yService) 
{_xService =xService,_YService= yService}

[HTTPPost]
ActionResult Create(A1 a1)
{
  _xService.XUnitOfWork.A1.add(a1)
  _xService.XUnitOfwork.SaveChanges();

} 

//其中X,Y是不同的上下文,具体类,没有实现接口!

在业务层

Xservice(XUnitofWork) // 没有实现接口!

在 DAL 层中

'XUnitofWork:DataRepostory(Generic)...
    {
      GenericRepository<a1Entity> A1,
      GenericRepository<a2Entity> A2
    }

现在我意识到我们应该在 BAL 层和 Web 层都实现接口。 我的问题是有什么方法可以模拟我们控制器中的服务(XService,YService)来测试某些行为(TDD)[例如,通过'_xService.XUnitOfwork.SaveChanges()'保存实体时发生保存更改异常?

请帮助。在此先感谢!

【问题讨论】:

    标签: asp.net-mvc-3 tdd moq unit-of-work


    【解决方案1】:

    如果您将具体类中的成员(属性、方法)标记为virtual,我认为您可以单独模拟这些方法/属性。 (我认为虚拟的 VB 等价物是 Overridable..?)

    Moq 通过在测试运行时在运行时创建新的具体实现来工作。这就是为什么它与接口和抽象类一起工作得如此好。但是如果没有接口或抽象类,则需要重写一个方法或属性。

    回复问题作者的回答:

    由于您自称是 TDD 新手,我只想指出,仅仅为了使类可测试而向类添加无参数构造函数不应该是可接受的解决方案。

    通过使您的 GenericRepository 类对实体框架的 DbSet / IDbSet 产生硬依赖,您正在创建存储库实现和 EF 之间的紧密耦合...请注意该文件顶部的 using System.Data.Entity 行。

    任何时候你决定添加一个构造函数依赖,你应该认真考虑将它添加为一个接口或抽象类。如果您需要访问您无法控制的库的成员(例如 EF 的 DbContext),请关注 Morten's answer 并将功能包装在您自己的自定义界面中。

    在 DbContext 的情况下,这个类不仅仅是为您提供一个 UnitOfWork 实现。它还为您提供了一种查询数据并在存储库中添加/替换/删除项目的方法:

    public interface IUnitOfWork
    {
        int SaveChanges();
    }
    
    public interface IQuery
    {
        IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class;
    }
    
    public interface ICommand : IQuery
    {
        void Add(object entity);
        void Replace(object entity);
        void Remove(object entity);
    }
    

    您可以很容易地将 DbContext 包装在这 3 个接口中,如下所示:

    public class MyCustomDbContext : DbContext, IUnitOfWork, ICommand
    {
        // DbContext already implements int SaveChanges()
    
        public IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class
        {
            return this.Set<TEntity>();
        }
    
        public void Add(object entity)
        {
            this.Entry(entity).State = EntityState.Added;
        }
    
        public void Replace(object entity)
        {
            this.Entry(entity).State = EntityState.Modified;
        }
    
        public void Remove(object entity)
        {
            this.Entry(entity).State = EntityState.Deleted;
        }
    }
    

    请注意您的接口如何不依赖于System.Data.Entity。它们使用原语和标准 .NET 类型,例如 objectIQueryable&lt;T&gt;int。这样,当您为接口提供通用存储库依赖项时,您可以删除对 System.Data.Entity 的依赖项:

    // using System.Data.Entity; // no need for this dependency any more
    
    public class GenericRepository
    {
        private readonly ICommand _entities;
        private readonly IQueryable<TEntity> _queryable;
    
        public GenericRepository(ICommand entities)
        {
            this._entities = entities;
            this._queryable = entities.GetQueryable<TEntity>();
        }
    
        //public GenericRepository()
        //{ 
            // no need for a parameterless constructor!
        //}
    }
    

    ...您的 GenericRepository 现在是完全可单元测试的,因为您可以轻松地模拟任何这些接口方法。

    最后说明:

    此外,在看到您对自己问题的回答后,您似乎将 CompanyRepository 作为 UnitOfWork 类的属性。然后,您将 UnitOfWork 作为对 CompanyInformationController 的依赖项注入。这是倒退。相反,您应该将 CompanyRepository(或其接口)注入控制器的构造函数。 UnitOfWork 模式与维护已知存储库的引用无关。它是关于跟踪对相关项目所做的多项更改,以便它们都可以作为单个事务推送一次。 EF 会自动执行此操作,因此只要 AutoFac 提供相同的 DbContext 实例,无论您的应用请求 IQuery、ICommand 还是 IUnitOfWork 实现,那么 UnitOfWork 应该关注的唯一方法是 SaveChanges()。

    【讨论】:

    • 感谢您的宝贵反馈!
    【解决方案2】:

    感谢您的回复。在花了几个小时并更改了我以前的代码后,我试图做的测试是成功的。 变化如下: 1)现在在我的控制器中使用 UnitofWork 而不是冗余服务。 2) 为 GenericRepository 类添加了一个无参数构造函数。(没有任何 DBContext!),因为它需要一个 DBContext 作为构造函数中的参数,不能通过提供 Mocked DBContext 来替代。

    通用存储库: 公共类 GenericRepository 其中 TEntity : 类 {

        internal DbContext _context;
        internal DbSet<TEntity> dbSet;
    
        public GenericRepository(DbContext context)
        {
            this._context = context;
            this.dbSet = context.Set<TEntity>();
        }
        public GenericRepository()    //newly added!
        { 
    
        }
    

    .......

    完成测试

        [TestMethod]
        public void Index_Return_OneModel_WhenCalling()
        {
            //arrange
    
            AutoMapperExtension automapper = new AutoMapperExtension();          
            var moqentities = new Mock<SetupEntities>();                
            List<CompanyInformation> list =new List<CompanyInformation>();
            list.Add(new CompanyInformation{ CompanyName = "a", CompanyAddress = "aa", Id = 1});
            list.Add(new CompanyInformation { CompanyName = "b", CompanyAddress = "b", Id = 2 });                 
            var unitOfWork = new Mock<UnitOfWork>(moqentities.Object);
            unitOfWork.Setup(d => d.CompanyRepository).Returns(new GenericRepository<CompanyInformation>());
            unitOfWork.Setup(d => d.CompanyRepository.GetAll()).Returns(list.AsQueryable());
    
            var controller = new CompanyInformationController(unitOfWork.Object);          
    
            //Act
            var result =(ViewResult) controller.Index();
            var model =(CompanyInformationViewModel)  result.ViewData.Model;
    
            //Assert
            Assert.AreEqual(1, model.Id);           
    
        } 
    

    【讨论】:

      【解决方案3】:

      最好的方法是为 XService 创建一个接口。如果由于某种原因这是不可能的(如果 XService 是不实现接口的第三方类),则考虑将功能包装在具有接口的包装类中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-11-07
        • 2020-08-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多