【问题标题】:Unit Test Fails for Unit Of Work Test工作单元测试的单元测试失败
【发布时间】:2013-09-17 14:19:01
【问题描述】:

这是我的测试:

[TestMethod]
public void TestUnitOfWork()
{
    UnitOfWork unitOfWork = new UnitOfWork();

    unitOfWork.ContactRepository.Insert(new Contact
    {
        Id = Guid.NewGuid(),
        FirstName = "Dom",
        LastName = "A",
        Email = "dominicarchual@yahoo.com"
    });

    var contacts = unitOfWork.ContactRepository.Get(x => x.FirstName == "Dominic");

    Assert.AreEqual(1, contacts.Count());
}

我得到的错误是:

测试方法 MvcContacts.Tests.Controllers.HomeControllerTest.TestUnitOfWork 抛出 异常:System.Data.ProviderIncompatibleException:错误 从数据库获取提供者信息时发生。这 可能是由 Entity Framework 使用不正确的连接引起的 细绳。检查内部异常以获取详细信息并确保 连接字符串是正确的。 ---> System.Data.ProviderIncompatibleException:提供者未返回 ProviderManifestToken 字符串。 ---> System.Data.SqlClient.SqlException:网络相关或 建立连接时发生特定于实例的错误 SQL 服务器。服务器未找到或无法访问。核实 实例名称正确且 SQL Server 配置为 允许远程连接。 (提供者:SQL 网络接口,错误:26 - 定位服务器/指定实例时出错)

我没有设置任何数据库;即我的上下文如下所示:

namespace MvcContacts.DAL
{
    public class ContactsContext : DbContext
    {
        public DbSet<Contact> Contacts { get; set; }
    }
}

我不知道如何将它映射到我的数据库;但是,我认为我不必这样做,因为我只是想使用模拟数据进行测试。我错了吗?

E1:这是我的工作单元。

namespace MvcContacts.DAL
{
    public class UnitOfWork : IDisposable
    {
        private ContactsContext context = new ContactsContext();
        private GenericRepository<Contact> contactRepository;

        public GenericRepository<Contact> ContactRepository
        {
            get
            {
                if (this.contactRepository == null)
                {
                    this.contactRepository = new GenericRepository<Contact>(context);
                }
                return contactRepository;
            }
        }

        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

【问题讨论】:

  • 请发布 UnitOfWork 的实现。看来您正在使用真正的实现而不是模拟一个
  • @PavelMurygin 我假设你的意思是定义?因为,实现在测试中是正确的(应该是,不是吗?)。

标签: c# asp.net-mvc unit-testing unit-of-work


【解决方案1】:

正如我所说,问题在于您实际上是在 UnitOfWork 中调用真实数据库。我很确定,您的 GenericRepository&lt;&gt; 类只是将 DbSet 包装在您的上下文中。这里是您创建“真正的”数据库访问器的地方。

private ContactsContext context = new ContactsContext();

但问题是您误解了存储库的整个概念。工作单元是一些数据源的抽象。您不应该对抽象进行单元测试,而应该对依赖它的某些功能进行单元测试。顺便说一句,DbContext 本身就是该定义的工作单元(来自 martinfowler.com):

维护受业务事务影响的对象列表,并协调更改的写入和并发问题的解决。

人们为什么不保持原样?因为它有一个缺陷。让我举例说明。好像您正在学习 ASP.Net MVC,所以让我们编写一些控制器:

public class ContactsController
{
    public ActionResult Index(int pageSize, int currentPage)
    {
         using(var db = new MvcLearningContext())
         {
             var contacts = db.Contacts
                              .Skip((currentPage - 1) * pageSize)
                              .Take(pageSize)
                              .ToList();
             return View(contacts);
         }
    }
}

您可能知道,MVC 的一大优势是能够对控制器逻辑进行单元测试。因此,让我们尝试编写一个简单的单元测试,以确保控制器操作不会返回超过给定页面大小的条目:

[TestMethod]
public void IndexShouldNotReturnMoreThanPageSizeResults()
{
    // arrange
    var controller = new ContactsController();

    // act
    var view = (ViewResult) controller.Index(10, 1);

    // assert  
    var Model = (IEnumerable<Contact>) view.Model;
    Assert.IsTrue(view.Model.Count() <= 10)
}

但是等等...我们想在单元测试中查询真实的数据库。 EF 的DbContext 的问题来了:它完全取决于真实的数据库。但是我们怎样才能避免呢? UnitOfWork 发挥作用:

public class ContactsController
{
    private UnitOfWorkFactoryBase _factory { get; set; }

    public ContactsController(UnitOfWorkFactoryBase factory)
    {
        factory = _factory;
    }

    public ActionResult Index(int pageSize, int currentPage)
    {
         using(var db = _factory.Create())
         {
             var contacts = db.Contacts
                              .Skip((currentPage - 1) * pageSize)
                              .Take(pageSize)
                              .ToList();
             return View(contacts);
         }
    }
}

单元测试代码:

[TestMethod]
public void IndexShouldNotReturnMoreThanPageSizeResults()
{
    // arrange
    var factory = new MockUnitOfWorkFactory();
    var controller = new ContactsController(factory);

    // act
    var view = (ViewResult) controller.Index(10, 1);

    // assert  
    var Model = (IEnumerable<Contact>) view.Model;
    Assert.IsTrue(view.Model.Count() <= 10)
}

在生产中,您将 MockUnitOfWorkFactory 替换为 UnitOfWorkFactory

UPD:工厂的基本实现:

public abstract class UnitOfWorkFactoryBase
{
    public abstract UnitOfWorkBase Create();
}

public class UnitOfWorkFactory : UnitOfWorkFactoryBase
{
    public override UnitOfWorkBase Create()
    {
        return new UnitOfWork();
    }
}

public class MockUnitOfWorkFactory : UnitOfWorkFactoryBase
{
    public override UnitOfWorkBase Create()
    {
        return new MockUnitOfWork();
    }
}

UnitOfWorkMockUnitOfWork 是 UnitOfWorkBase 抽象类的实现。

【讨论】:

  • 感谢您提供此信息,但我不确定要更改哪些内容才能使我的代码正常工作。可能相关:我不明白UnitOfWorkFactory 的定义位置以及应该如何定义。能否请您再为我解释一下?
  • 你应该删除你的单元测试,这很奇怪:)另外,你应该为工作单元和两个具体实现创建一个抽象类,一个在内部使用 DbContext 并基于内存存储库......好吧,这是一个很大的话题,给我写信 pavel.murygin@gmail.com。明天我也许可以帮助你
  • 我只是不知道MockUnitOfWorkFactoryUnitOfWorkFactory 应该是什么样子。我一直在尝试通过遵循本教程 asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/… 来学习这一点
  • @user1477388 很难在一个答案中解释所有内容。尤其是当顶级开发人员之间也有很多讨论时。在这里,我在 SE 上找到了一个不错的帖子:codereview.stackexchange.com/questions/11785/…
  • 我认为Index 方法中的ContactsController 类中有错字:factory 应该是this._factory
猜你喜欢
  • 1970-01-01
  • 2014-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-15
  • 2014-04-13
  • 2017-08-15
相关资源
最近更新 更多