问题描述
我试图在单元测试中嘲笑,并将单元测试流程整合到我的项目中。所以我一直走几个教程,并重构我的代码来支持嘲笑,无论如何,我无法通过测试,因为我试图测试的DB方法是使用一个事务,但是当创建一个事务时,我得到没有交易一切都很好。
我现在的代码是:
[TestMethod]
public void Test1()
{
var mockSet = GetDbMock();
var mockContext = new Mock< DataContext>();
mockContext.Setup(m => m.Repository).Returns(mockSet.Object);
var service = new MyService(mockContext.Object);
service.SaveRepository(GetRepositoryData()。First());
mockSet.Verify(m => m.Remove(It.IsAny< Repository>()),Times.Once());
mockSet.Verify(m => m.Add(It.IsAny< Repository>()),Times.Once());
mockContext.Verify(m => m.SaveChanges(),Times.Once());
}
//获取一个现有项的DbSet模拟
private Mock< DbSet< Repository>> GetDbMock()
{
var data = GetRepositoryData();
var mockSet = new Mock< DbSet< Repository>>();
mockSet.As< IQueryable< Repository>()。Setup(m => m.Provider).Returns(data.Provider);
//为简洁而跳过
return mockSet;
}
测试代码:
私人readonly DataContext _context;
public MyService(DataContext ctx)
{
_context = ctx;
}
public void SaveRepositories(Repository repo)
{
using(_context)
{
//这里的事务创建失败
using(var transaction = _context.Database.BeginTransaction())
{
DeleteExistingEntries(repo.Id);
AddRepositories(repo);
_context.SaveChanges();
transaction.Commit();
}
}
}
我试图嘲笑交易部分:
var mockTransaction = new Mock< DbContextTransaction>();
mockContext.Setup(x => x.Database.BeginTransaction())。Returns(mockTransaction.Object);
但这不工作,失败:
无虚拟(VB中可覆盖)成员设置无效:conn =>
conn.Database.BeginTransaction()
任何想法如何解决?
第二个错误消息说, Moq不能模拟非虚拟方法或属性,所以这种方法将不起作用。我建议您使用来解决此问题。这个想法是创建一个与 DataContext
进行交互的适配器(实现一些接口的包装类),并通过该方式执行所有的数据库活动接口。然后,您可以模拟界面。
public interface IDataContext {
DbSet< Repository>存储库{get; }
DbContextTransaction BeginTransaction();
}
public class DataContextAdapter {
private readonly DataContext _dataContext;
public DataContextAdapter(DataContext dataContext){
_dataContext = dataContext;
}
public DbSet< Repository> Repository {get {return _dataContext.Repository; }}
public DbContextTransaction BeginTransaction(){
return _dataContext.Database.BeginTransaction();
}
}
以前使用 DataContext
直接现在应该使用一个 IDataContext
,它应该是一个 DataContextAdapter
该程序正在运行,但在测试中,您可以轻松地模拟 IDataContext
。这应该使嘲笑的方式更简单,因为您可以设计 IDataContext
和 DataContextAdapter
来隐藏实际的一些复杂性 DataContext
。
I am trying to make sense of mocking in unit testing and to integrate the unit testing process to my project. So I have been walking thru several tutorials and refactoring my code to support mocking, anyway, I am unable to pass the tests, because the DB method I am trying to test is using a transaction, but when creating a transaction, I get
Without transaction everything works just fine.
The code I currently have is:
[TestMethod]
public void Test1()
{
var mockSet = GetDbMock();
var mockContext = new Mock<DataContext>();
mockContext.Setup(m => m.Repository).Returns(mockSet.Object);
var service = new MyService(mockContext.Object);
service.SaveRepository(GetRepositoryData().First());
mockSet.Verify(m => m.Remove(It.IsAny<Repository>()), Times.Once());
mockSet.Verify(m => m.Add(It.IsAny<Repository>()), Times.Once());
mockContext.Verify(m => m.SaveChanges(), Times.Once());
}
// gets the DbSet mock with one existing item
private Mock<DbSet<Repository>> GetDbMock()
{
var data = GetRepositoryData();
var mockSet = new Mock<DbSet<Repository>>();
mockSet.As<IQueryable<Repository>>().Setup(m => m.Provider).Returns(data.Provider);
// skipped for brevity
return mockSet;
}
Code under test:
private readonly DataContext _context;
public MyService(DataContext ctx)
{
_context = ctx;
}
public void SaveRepositories(Repository repo)
{
using (_context)
{
// Here the transaction creation fails
using (var transaction = _context.Database.BeginTransaction())
{
DeleteExistingEntries(repo.Id);
AddRepositories(repo);
_context.SaveChanges();
transaction.Commit();
}
}
}
I was trying to mock the transaction part as well:
var mockTransaction = new Mock<DbContextTransaction>();
mockContext.Setup(x => x.Database.BeginTransaction()).Returns(mockTransaction.Object);
but this is not working, failing with:
Any ideas how to solve this?
As the second error message says, Moq can't mock non-virtual methods or properties, so this approach won't work. I suggest using the Adapter pattern to work around this. The idea is to create an adapter (a wrapper class that implements some interface) that interacts with the DataContext
, and to perform all database activity through that interface. Then, you can mock the interface instead.
public interface IDataContext {
DbSet<Repository> Repository { get; }
DbContextTransaction BeginTransaction();
}
public class DataContextAdapter {
private readonly DataContext _dataContext;
public DataContextAdapter(DataContext dataContext) {
_dataContext = dataContext;
}
public DbSet<Repository> Repository { get { return _dataContext.Repository; } }
public DbContextTransaction BeginTransaction() {
return _dataContext.Database.BeginTransaction();
}
}
All of your code that previously used the DataContext
directly should now use an IDataContext
, which should be a DataContextAdapter
when the program is running, but in a test, you can easily mock IDataContext
. This should make the mocking way simpler too because you can design IDataContext
and DataContextAdapter
to hide some of the complexities of the actual DataContext
.
这篇关于过去实体框架BeginTransaction的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!