我是TDD(Asp.net MVC3环境)的新手,并试图采用TDD作为我们更好的开发方法。
在我们的生产代码中,我们有以下方案
//Autofac used to resolve Dependency
TestController(XService xSerivice,YSerivice yService)
{_xService =xService,_YService= yService}
[HTTPPost]
ActionResult Create(A1 a1)
{
_xService.XUnitOfWork.A1.add(a1)
_xService.XUnitOfwork.SaveChanges();
}
//其中X,Y是不同的上下文,具体类,没有实现接口!
Xservice(XUnitofWork)
//没有实现任何界面!
在DAL图层
'XUnitofWork:DataRepostory(Generic)...
{
GenericRepository<a1Entity> A1,
GenericRepository<a2Entity> A2
}
现在我意识到我们应该在BAL和Web层中实现接口。
我的问题是,有什么方法可以在我们的控制器中模拟服务(XService,YService)来测试某些行为(TDD)[例如,在通过'_xService.XUnitOfwork.SaveChanges()
'保存实体时发生保存更改异常?
请帮忙。谢谢!
答案 0 :(得分:2)
如果将具体类中的成员(属性,方法)标记为virtual
,我认为您可以单独模拟这些方法/属性。 (我认为虚拟机的VB等价物是Overridable ..?)
Moq的工作原理是在测试运行时在运行时创建一些新的具体实现。这就是它与接口和抽象类一起工作的原因。但是如果没有接口或抽象类,则需要覆盖方法或属性。
回复问题作者的回答:
由于你是一个自称为TDD的新手,我只是想指出,为了使类可测试而向类中添加无参数构造函数不应该是一个可接受的解决方案。
通过为您的GenericRepository类提供对Entity Framework的DbSet / IDbSet的硬依赖,您将在您的存储库实现与EF之间建立紧密耦合...请注意该文件顶部的using System.Data.Entity
行。
每当您决定添加构造函数依赖项时,您应该认真考虑将其添加为接口或抽象类。如果您需要访问您无法控制的库的成员(如EF的DbContext),请按照Morten's answer并将功能包装在您自己的自定义界面中。
对于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包装在这三个界面中,如下所示:
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()。
答案 1 :(得分:0)
感谢您的回复。我花了几个小时后改变我之前的代码,试图做的测试是成功的。 变化如下: 1)现在在我的控制器中使用UnitofWork而不是冗余服务。 2)在GenericRepository类中添加了一个参数less构造函数(没有任何DBContext!),因为它将DBContext作为Constructor中的参数重新命名,而不能通过提供Mocked DBContext来替换它。
<强> GenericRepository:强> 公共类GenericRepository,其中TEntity:class {
internal DbContext _context;
internal DbSet<TEntity> dbSet;
public GenericRepository(DbContext context)
{
this._context = context;
this.dbSet = context.Set<TEntity>();
}
public GenericRepository() //newly added!
{
}
...............
完成测试
[TestMethod]
public void Index_Return_OneModel_WhenCalling()
{
//arrange
AutoMapperExtension automapper = new AutoMapperExtension();
var moqentities = new Mock<SetupEntities>();
List<CompanyInformation> list =new List<CompanyInformation>();
list.Add(new CompanyInformation{ CompanyName = "a", CompanyAddress = "aa", Id = 1});
list.Add(new CompanyInformation { CompanyName = "b", CompanyAddress = "b", Id = 2 });
var unitOfWork = new Mock<UnitOfWork>(moqentities.Object);
unitOfWork.Setup(d => d.CompanyRepository).Returns(new GenericRepository<CompanyInformation>());
unitOfWork.Setup(d => d.CompanyRepository.GetAll()).Returns(list.AsQueryable());
var controller = new CompanyInformationController(unitOfWork.Object);
//Act
var result =(ViewResult) controller.Index();
var model =(CompanyInformationViewModel) result.ViewData.Model;
//Assert
Assert.AreEqual(1, model.Id);
}
答案 2 :(得分:0)
最好的方法是为XService创建一个接口。如果由于某种原因这是不可能的(如果XService是没有实现接口的第三方类),那么考虑将功能包装在具有接口的包装类中。