我正在通过Apress ASP.NET MVC 3 book并尝试确保我为所有可能的条件创建单元测试,但是花了一天的大部分时间试图弄清楚为什么编辑内容无法保存的原因(请参见this SO question),我想创建一个单元为此测试。
我已经计算出需要为以下课程创建一个单元测试:
public class EFProductRepository : IProductRepository {
private EFDbContext context = new EFDbContext();
public IQueryable<Product> Products {
get { return context.Products; }
}
public void SaveProduct(Product product) {
if (product.ProductID == 0) {
context.Products.Add(product);
}
context.SaveChanges();
}
public void DeleteProduct(Product product) {
context.Products.Remove(product);
context.SaveChanges();
}
}
public class EFDbContext : DbContext {
public DbSet<Product> Products { get; set; }
}
我正在使用Ninject.MVC3和Moq,并且在之前(虽然通过前面提到的书进行工作时)已经创建了多个单元测试,所以我慢慢将其绕开。我已经(希望正确地)创建了一个构造函数方法以使我能够传递
_context
:public class EFProductRepository : IProductRepository {
private EFDbContext _context;
// constructor
public EFProductRepository(EFDbContext context) {
_context = context;
}
public IQueryable<Product> Products {
get { return _context.Products; }
}
public void SaveProduct(Product product) {
if (product.ProductID == 0) {
_context.Products.Add(product);
} else {
_context.Entry(product).State = EntityState.Modified;
}
_context.SaveChanges();
}
public void DeleteProduct(Product product) {
_context.Products.Remove(product);
_context.SaveChanges();
}
}
但是,这是我开始遇到麻烦的地方...我相信我需要为
EFDbContext
创建一个接口(请参见下文),以便可以将其替换为模拟回购以用于测试,但它基于类DbContext
:public class EFDbContext : DbContext {
public DbSet<Product> Products { get; set; }
}
从
System.Data.Entity
开始,我终生无法解决如何为其创建接口的问题...如果创建以下接口,由于缺少.SaveChanges()
的方法DbContext
而导致错误>类,我无法使用“ DbContext”构建接口,例如`EFDbContext是因为它是一个类而不是接口...using System;
using System.Data.Entity;
using SportsStore.Domain.Entities;
namespace SportsStore.Domain.Concrete {
interface IEFDbContext {
DbSet<Product> Products { get; set; }
}
}
可以从this page上的“源代码/下载”获取原始源,以防万一我错过了上述代码片段中的某些内容(或者只是询问,我将其添加)。
我已经达到了我所能理解的极限,而且无论我搜索或阅读的内容如何,我似乎都无法弄清如何克服这一困难。请帮忙!
最佳答案
这里的问题是您还没有足够抽象。抽象/接口的重点是定义一个以技术不可知的方式公开行为的协定。
换句话说,为EFDbContext创建一个接口是一个很好的第一步,但是该接口仍与具体的实现DbSet(DbSet)相关联。
快速解决方案是将此属性公开为IDbSet而不是DbSet。理想情况下,您可以公开一些更抽象的内容,例如IQueryable(尽管这并不能为您提供Add()方法等)。越抽象,就越容易模拟。
然后,剩下的就是您要完成的其余“合同”,即SaveChanges()方法。
您更新的代码如下所示:
public class EFProductRepository : IProductRepository {
private IEFDbContext context;
public EFProductRepository(IEFDbContext context) {
this.context = context;
}
...
}
public interface IEFDbContext {
IDbSet<Product> Products { get; set; }
void SaveChanges();
}
但是...您要问的主要问题是:您要测试什么(相反,您要模拟/避免测试什么)?换句话说:正在尝试验证保存某些内容时应用程序的工作方式,还是正在测试实际的保存方式。
如果您只是测试应用程序的工作方式,并且不关心实际保存到数据库中,那么我会考虑在更高级别进行模拟-IProductRepository。然后,您根本不会访问数据库。
如果要确保您的对象实际上已持久存储到数据库中,那么您应该点击DbContext,并且根本不想模拟该部分。
就个人而言,我认为这两种情况都是不同的,并且同等重要,并且我分别为每种情况编写了一个测试:一个用于测试我的应用程序是否应执行的工作,另一个用于测试数据库交互是否有效。