Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/273.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 模拟数据库事务?_C#_Entity Framework_Unit Testing_Asp.net Mvc 4_Moq - Fatal编程技术网

C# 模拟数据库事务?

C# 模拟数据库事务?,c#,entity-framework,unit-testing,asp.net-mvc-4,moq,C#,Entity Framework,Unit Testing,Asp.net Mvc 4,Moq,我有一对具有父/子关系的表-事件和意外详细信息。我有一个viewmodel,它包含这两个表中的信息。我有一个业务层方法,它被传递给需要更新两个表的viewmodel实例 因此,在该方法中,我使用了EF6的新事务机制: using (var transaction = this.db.Database.BeginTransaction()) { try { // various database stuff this.db.SaveChanges()

我有一对具有父/子关系的表-事件和意外详细信息。我有一个viewmodel,它包含这两个表中的信息。我有一个业务层方法,它被传递给需要更新两个表的viewmodel实例

因此,在该方法中,我使用了EF6的新事务机制:

using (var transaction = this.db.Database.BeginTransaction())
{
    try
    {
        // various database stuff
        this.db.SaveChanges();
        // more database stuff
        this.db.SaveChanges();
        // yet more database stuff
        this.db.SaveChanges();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        this.logger.logException(ex, "Exception caught in transaction, rolling back");
        throw;
    }
}
所以,我的问题。我如何测试这个

我正在使用微软的单元测试框架和Moq,我在模拟dbcontext和dbset方面没有遇到任何问题,但我似乎不知道如何绕过事务

如果我不尝试模拟事务,我会得到一个InvalidOperationException:

“在应用程序中找不到名为xxx的连接字符串 配置文件。“

这很有道理——没有应用程序配置文件,也没有任何数据库

但如果我尝试模拟BeginTransaction(),则会出现初始化错误:NotSupportedException:

“非虚拟成员上的设置无效:m=> m、 数据库。BeginTransaction”

这让我陷入了困境,研究了.NET方法的反编译,试图识别一些可能来自可用接口的类,或者我可以在其中注入模拟对象的类

我并不是试图对MS的事务代码进行单元测试——我只是想确保对每个表中的相应记录进行适当的更改。但就目前情况来看,这似乎是不可测试的,任何使用事务的方法都是不可测试的。这只是一种痛苦


我在谷歌上搜索了一下,没有找到任何有用的东西。有人碰到过这个问题吗?有人对如何进行有想法吗?

测试这类东西总是很复杂,但首先你应该问问自己,你是想对业务逻辑进行单元测试,还是想对应用程序进行集成测试

如果您想要单元测试您的逻辑,您基本上不应该尝试模拟实体框架,因为您不想测试EF,您只想测试您的代码,对吗? 为此,模拟任何数据访问对象,只对业务逻辑进行单元测试


但是,如果您想测试数据访问层是否工作,例如,如果您的代码能够处理您已经实现的所有CRUD操作,那么您应该针对真实的数据库进行集成测试。在这种情况下,不要试图模拟任何数据访问对象(EF),只需针对测试数据库或sql express localDB运行测试即可。

您可以将EF类表示为POCO类,并隔离数据库适配器类中的所有数据库交互。这些适配器类将有一个在测试业务逻辑时可以模拟的接口

适配器类中的数据库操作可以使用真实的数据库连接进行测试,但是可以使用专用的数据库和连接字符串进行单元测试

那么,测试封装在事务中的业务代码怎么样

为了将业务代码与数据库适配器隔离开来,您必须为EF事务范围创建一个可以模拟的接口

我以前使用过这样的设计,虽然没有使用EF,但使用了类似的POCO包装(在伪C#中,没有检查语法或合理性):

上述设计应允许您为实体框架操作创建单元测试,而不必担心业务逻辑或事务,并为业务逻辑创建单元测试,您可以在其中模拟数据库故障,并使用MOQ或类似工具检查在ITransactionScope模拟中是否实际调用了回滚。 通过上面的内容,您应该能够涵盖业务逻辑中任何阶段的几乎所有事务失败


当然,您应该用一些好的集成测试来补充您的单元测试,因为事务可能很棘手,特别棘手的死锁可能会在并发使用时发生,并且在模拟测试中很难捕捉到这些死锁。

您可以将上下文和事务包装在接口中,然后通过某些提供程序类实现接口:

public interface IDbContextProvider
{
    YourContext Context { get; set; }
    DbContextTransaction DbTransaction { get; set; }
    void Commit();
    void Rollback();
    void BeginTransaction();
    void SaveChanges();
}
然后实施它:

public class EfContextProvider : IDbContextProvider
{
    public EfContextProvider(YourContext context)
    {
        Context = context;
    }
    public YourContext Context { set; get; }
    public DbContextTransaction DbTransaction { set; get; }

    public void Commit()
    {
        DbTransaction.Commit();
    }

    public void Rollback()
    {
        DbTransaction.Rollback();
    }

    public void BeginTransaction()
    {
        DbTransaction=Context.Database.BeginTransaction();
    }

    public void SaveChanges()
    {
        Context.SaveChanges();
    }
}

因此,现在为您的类提供IDbContextProvider依赖项并使用它(它内部也有上下文)。可以用_contextProvider.BeginTransaction()替换using块;然后还有_contextProvider.Commit();或_contextProvider.Rollback()

我花了几个小时试图弄明白,我相信这可以由MS Fakes直接完成,无需包装或新的classe

您需要执行三个步骤:

  • 为DbContextTransaction创建垫片对象,并绕开其提交和回滚方法而不做任何操作
  • 为数据库创建垫片对象。并绕过其BeginTransaction方法返回在步骤1中创建的DbContextTransaction shim对象
  • 所有实例的Detour DbContext.Database属性返回步骤2中创建的数据库填充对象
  • 就这些

        static void SetupDBTransaction()
        {
            System.Data.Entity.Fakes.ShimDbContextTransaction transaction = new System.Data.Entity.Fakes.ShimDbContextTransaction();
            transaction.Commit = () => { };
            transaction.Rollback = () => { };
    
            System.Data.Entity.Fakes.ShimDatabase database = new System.Data.Entity.Fakes.ShimDatabase();
            database.BeginTransactionIsolationLevel = (isolationLevel) =>{return transaction.Instance;};
    
            System.Data.Entity.Fakes.ShimDbContext.AllInstances.DatabaseGet = (@this) => { return database.Instance; };
        }
    

    您需要的是可以调用Commit()和Rollback()的东西,其形状类似于System.Data.Entity.DbContextTransaction,对吗?因此,事实证明,您可以在任何真实数据库上使用真实的DbContextTransaction。然后,只要您的测试代码没有对用于事务的数据库进行任何实际更改,Commit()或Rollback()就会成功,并且什么也不做

    在我的应用程序中,WebAPI层需要在db事务中执行两个业务逻辑操作,这样,如果第二个操作出错,第一个操作就不会发生。我向业务逻辑接口添加了一个方法,以返回web api层可以使用的事务。在对该代码的测试中,我模拟了在空测试数据库上返回DbContextTransaction的方法。以下是我使用的设置代码:

    var scope = (new PConn.DataAccess.PressConnEntities()).Database.BeginTransaction();
    var bizl = new Mock<IOrderMgr>();
    bizl.Setup(m => m.CreateNewOrder(7, It.IsAny<string>(), It.IsAny<string>())).Returns(_testOrder1);
    // .GetOrdersQuery(channel, beginUTC, endUTC);
    bizl.Setup(m => m.GetOrdersQuery(7, It.IsAny<DateTime>(), It.IsAny<DateTime>())).Returns(matchedOrdersList.AsQueryable());
    bizl.Setup(m => m.BeginTransaction()).Returns(scope);
    

    我们实现了ivaylo pashov的解决方案以及以下代码:

    //Dependency Injection
    public static void RegisterTypes(IUnityContainer container)
            {
                // Register manager mappings.
                container.RegisterType<IDatabaseContextProvider, EntityContextProvider>(new PerResolveLifetimeManager());
            }
        }
    
    //Test Setup
            /// <summary>
            ///     Mocked <see cref="IrdEntities" /> context to be used in testing.
            /// </summary>
            private Mock<CCMSEntities> _irdContextMock;
            /// <summary>
            ///     Mocked <see cref="IDatabaseContextProvider" /> context to be used in testing.
            /// </summary>
            private Mock<IDatabaseContextProvider> _EntityContextProvider;
    ...
    
                _irdContextMock = new Mock<CCMSEntities>();
                _irdContextMock.Setup(m => m.Outbreaks).Returns(new Mock<DbSet<Outbreak>>().SetupData(_outbreakData).Object);
                _irdContextMock.Setup(m => m.FDI_Number_Counter).Returns(new Mock<DbSet<FDI_Number_Counter>>().SetupData(new List<FDI_Number_Counter>()).Object);
    
                _EntityContextProvider = new Mock<IDatabaseContextProvider>();
                _EntityContextProvider.Setup(m => m.Context).Returns(_irdContextMock.Object);
    
                _irdOutbreakRepository = new IrdOutbreakRepository(_EntityContextProvider.Object, _loggerMock.Object);
    
    // Usage in the Class being tested:
    //Constructor
            public IrdOutbreakRepository(IDatabaseContextProvider entityContextProvider, ILogger logger)
            {
                _entityContextProvider = entityContextProvider;
                _irdContext = entityContextProvider.Context;
                _logger = logger;
            }
    
            /// <summary>
            ///     The wrapper for the Entity Framework context and transaction.
            /// </summary>
            private readonly IDatabaseContextProvider _entityContextProvider;
    
            // The usage of a transaction that automatically gets mocked because the return type is void.
            _entityContextProvider.BeginTransaction();
    ...
    
    //依赖项注入
    公共静态无效注册表类型(IUnityContainer cont
    
    using (var scope = this.OrderManager.BeginTransaction())
    {
        PrintOrder pconnOrder = this.OrderManager.CreateNewOrder(channel, payload, claimsIdentity.Name);
        bool parseResult = this.OrderManager.ParseNewOrder(pconnOrder, claimsIdentity.Name, out parseErrorMessage);
    
        if (!parseResult)
        {
            // return a fault to the caller
            HttpResponseMessage respMsg = new HttpResponseMessage(HttpStatusCode.BadRequest);
            respMsg.Content = new StringContent(parseErrorMessage);
    
            throw (new HttpResponseException(respMsg));
        }
    
        scope.Commit();
        return (pconnOrder.PrintOrderID);
    }
    
    //Dependency Injection
    public static void RegisterTypes(IUnityContainer container)
            {
                // Register manager mappings.
                container.RegisterType<IDatabaseContextProvider, EntityContextProvider>(new PerResolveLifetimeManager());
            }
        }
    
    //Test Setup
            /// <summary>
            ///     Mocked <see cref="IrdEntities" /> context to be used in testing.
            /// </summary>
            private Mock<CCMSEntities> _irdContextMock;
            /// <summary>
            ///     Mocked <see cref="IDatabaseContextProvider" /> context to be used in testing.
            /// </summary>
            private Mock<IDatabaseContextProvider> _EntityContextProvider;
    ...
    
                _irdContextMock = new Mock<CCMSEntities>();
                _irdContextMock.Setup(m => m.Outbreaks).Returns(new Mock<DbSet<Outbreak>>().SetupData(_outbreakData).Object);
                _irdContextMock.Setup(m => m.FDI_Number_Counter).Returns(new Mock<DbSet<FDI_Number_Counter>>().SetupData(new List<FDI_Number_Counter>()).Object);
    
                _EntityContextProvider = new Mock<IDatabaseContextProvider>();
                _EntityContextProvider.Setup(m => m.Context).Returns(_irdContextMock.Object);
    
                _irdOutbreakRepository = new IrdOutbreakRepository(_EntityContextProvider.Object, _loggerMock.Object);
    
    // Usage in the Class being tested:
    //Constructor
            public IrdOutbreakRepository(IDatabaseContextProvider entityContextProvider, ILogger logger)
            {
                _entityContextProvider = entityContextProvider;
                _irdContext = entityContextProvider.Context;
                _logger = logger;
            }
    
            /// <summary>
            ///     The wrapper for the Entity Framework context and transaction.
            /// </summary>
            private readonly IDatabaseContextProvider _entityContextProvider;
    
            // The usage of a transaction that automatically gets mocked because the return type is void.
            _entityContextProvider.BeginTransaction();
    ...