【问题标题】:ASP.NET Testing DbContext in local methodsASP.NET 在本地方法中测试 DbContext
【发布时间】:2015-08-20 05:28:27
【问题描述】:

我有一个 ASP.NET MVC 项目,并且正在按照以下内容编写我的服务:

public class UserService : IUserService
{
    public void AddUser(UserModel userModel)
    {
        if (ModelState.IsValid){
            var user = new User
            {
                FirstName = userModel.FirstName,
                LastName = userModel.LastName,
                Email = userModel.Email,
                Age = userModel.Age
            };
            using (var context = new MyDbContext())
            {
                context.Users.Add(user);
            }
        }
    }
    //... More code for service
}

还有我的 DbContext 类:

public class MyDbContext : DbContext 
{
    public MyDbContext(): base("name=mydb"){}

    public DbSet<User> Users {get; set;}

    //.. More domain objects.
}

这在大多数情况下运行良好,但是当我想尝试测试它时就会出现问题。我遵循一般准则here 将其模拟出来,但前提是 DbContext 被声明为类实例,而不是函数变量。我想这样做是因为它使我能够控制事务和上下文处理。有人对如何做到这一点有任何建议吗?

我的模拟是这样的:

public class UserServiceTest 
{
    [Fact]
    public void TestAddUser()
    {
        var userSet = GetQueryableMockDbSet(userData.ToArray());
        var context = new MyDbContext
        {
            Users = userSet
        };
        var userService = new UserService();
        userService.AddUser(); // HOW DO I GET CONTEXT IN HERE?
    }   

    private static DbSet<T> GetQueryableMockDbSet<T>(params T[] sourceList) where T : class
        {
            var queryable = sourceList.AsQueryable();

            var dbSet = new Mock<DbSet<T>>();
            dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
            dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
            dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
            dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());

            return dbSet.Object;
        }
}

【问题讨论】:

  • 您需要将DbContext 传递给服务的构造函数。阅读依赖注入并使用一些 IoC 容器使事情变得更容易
  • 他不能将实例传递给容器,因为他说他想控制方法内实例的创建。我在想也许 OP 可以将Factory 注入将创建实例的服务中,然后使用模拟对象模拟工厂创建方法
  • 工厂方法看起来很有希望:它会不会像 FactoryContext.Create() 返回一个 MyDbContext 项? Create 方法显然是静态的。
  • 是的@IvanPeng 这正是我的想法,请参阅下面的答案

标签: c# asp.net-mvc integration-testing


【解决方案1】:

实现目标的一种方法

我想这样做是因为它使我能够控制事务和上下文处理。

会做一些类似的事情

public class UserService : IUserService {
   private ContextFactory _ContextFactory;

   public UserService(ContextFactory contextFactory) {
       _ContextFactory = contextFactory;
   }

   public void AddUser(UserModel userModel) {
       ...
       using (var context = _ContextFactory.CreateInstance()) {
           context.Users.Add(user);
       }
   }

}

这将允许您注入 ContextFactory 的不同实例,这将使用 Mock 创建 DbContext

【讨论】:

    【解决方案2】:

    我肯定更喜欢 IOC 方法,但如果您想要一个快速解决方案而无需重写太多代码,您可以执行以下操作:

    public class UserService : IUserService 
    {
        public static Func<MyDbContext> CreateContext = () => new MyDbContext();
    
        public UserService ()
        {
        } 
    
        public void AddUser(UserModel userModel)
        {
            ...
            using (var context = CreateContext())
            {
                context.Users.Add(user);
            }
        }
    }
    

    您不必更改生产代码,但可以在您的 TestMethod 中更改

        var context = new MyDbContext
        {
            Users = userSet
        };
        UserService.CreateContext = () => context;
    

    【讨论】:

    • 虽然这在技术上不遵循最佳实践,这是我试图做的,但我仍然非常喜欢这个答案。简单而厚颜无耻。
    • 是的,如果您已经遵循 IOC 模式,将新的依赖项注入到类中是小菜一碟,但如果必须在代码中更改 50 个构造函数,这是一个不错的解决方案。我相信这是第三方库的常见模式(我想我已经在quartz.net 或 nuget.core 中看到了这一点)。它保持代码简单,不需要对 IOC 容器的依赖,但仍然保持注入对象的灵活性。
    【解决方案3】:

    您需要在服务的构造函数中传递DbContext。最简单的方法是开始使用任何 IoC 框架(AutoFac、Unity、Ninject 等)。要模拟数据库,最简单的方法是开始使用任何模拟库,例如 Moq 和 RhinoMocks。

    public class UserService : IUserService 
    {
        private MyDbContext _context;
    
        public UserService (MyDbContext dbContext)
        {
            _context = dbContext;
        } 
    }
    

    【讨论】:

    • 如上所述,我知道这种方法,但我宁愿不要,因为我想在这里控制我的交易,并管理上下文处置。
    • 但是您需要将上下文传递给服务。无论是在构造函数中还是在方法中。如果您使用 IoC 框架进行处理,他们会以良好的方式为您处理。
    • 当你编写测试时,测试类本身应该“拥有”dbContext(无论是否模拟)而不是被测类。
    猜你喜欢
    • 2018-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-09
    • 2011-03-22
    • 1970-01-01
    • 2016-07-17
    • 1970-01-01
    相关资源
    最近更新 更多