如果您将具体类中的成员(属性、方法)标记为virtual
,我认为您可以单独模拟这些方法/属性。(我认为虚拟的 VB 等价物是 Overridable..?)
Moq 通过在测试运行时在运行时创建新的具体实现来工作。这就是为什么它与接口和抽象类一起工作得如此好。但是如果没有接口或抽象类,则需要重写一个方法或属性。
回复问题作者的回答:
由于您自称是 TDD 新手,我只想指出,仅仅为了使类可测试而向类添加无参数构造函数不应该是一个可接受的解决方案。
通过为您的 GenericRepository 类提供对实体框架的 DbSet / IDbSet 的硬依赖,您正在创建存储库实现和 EF 之间的紧密耦合......请注意该using System.Data.Entity
文件顶部的行。
每当您决定添加构造函数依赖项时,您都应该认真考虑将其添加为接口或抽象类。如果您需要访问您无法控制的库的成员(例如 EF 的 DbContext),请按照Morten 的回答并将功能包装在您自己的自定义界面中。
在 DbContext 的情况下,此类不仅仅是为您提供 UnitOfWork 实现。它还为您提供了一种查询数据并在存储库中添加/替换/删除项目的方法:
public interface IUnitOfWork
{
int SaveChanges();
}
public interface IQuery
{
IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class;
}
public interface ICommand : IQuery
{
void Add(object entity);
void Replace(object entity);
void Remove(object entity);
}
您可以很容易地将 DbContext 包装在这 3 个接口中,如下所示:
public class MyCustomDbContext : DbContext, IUnitOfWork, ICommand
{
// DbContext already implements int SaveChanges()
public IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class
{
return this.Set<TEntity>();
}
public void Add(object entity)
{
this.Entry(entity).State = EntityState.Added;
}
public void Replace(object entity)
{
this.Entry(entity).State = EntityState.Modified;
}
public void Remove(object entity)
{
this.Entry(entity).State = EntityState.Deleted;
}
}
请注意您的接口如何不依赖于System.Data.Entity
. 它们使用原语和标准 .NET 类型,如object
、IQueryable<T>
和int
. 这样,当您为接口提供通用存储库依赖项时,您可以删除对 System.Data.Entity 的依赖项:
// using System.Data.Entity; // no need for this dependency any more
public class GenericRepository
{
private readonly ICommand _entities;
private readonly IQueryable<TEntity> _queryable;
public GenericRepository(ICommand entities)
{
this._entities = entities;
this._queryable = entities.GetQueryable<TEntity>();
}
//public GenericRepository()
//{
// no need for a parameterless constructor!
//}
}
...并且您的 GenericRepository 现在是完全可单元测试的,因为您可以轻松地模拟任何这些接口方法。
最后说明:
此外,在看到您对自己问题的回答后,您似乎将 CompanyRepository 作为 UnitOfWork 类的属性。然后,您将 UnitOfWork 作为对 CompanyInformationController 的依赖项注入。这是倒退。相反,您应该将 CompanyRepository(或其接口)注入控制器的构造函数。UnitOfWork 模式与维护已知存储库的引用无关。它是关于跟踪对相关项目所做的多项更改,以便它们都可以作为单个事务推送一次。EF 会自动执行此操作,因此只要 AutoFac 提供相同的 DbContext 实例,无论您的应用程序请求 IQuery、ICommand 还是 IUnitOfWork 实现,那么 UnitOfWork 应该关注的唯一方法是 SaveChanges()。