我刚刚浏览了有关模拟和依赖注入(inject)主题的问题和博客。得出结论,我只需要模拟客户端使用的接口(interface)。我期待在这里测试一个不知道的简单用例。
契约(Contract)
public Interface IApplicationService
{
bool DeleteApplication(int id);
ApplicationDto AddApplication(ApplicationDto application);
IEnumerable<ApplicationDto> GetApplications();
}
实现(我要 mock 了)
public Class ApplicationService:IApplicationService
{
private EntityFrameworkRepo repo;
public ApplicationService()
{
repo = new EntityFrameworkRepo();
}
public ApplicationDto Add(ApplicationDto dto)
{
//add to dbcontext and commit
}
}
模拟代码
[Test(Description = "Test If can successfully add application")]
public void CanAddApplication()
{
//create a mock application service
var applicationService = new Mock<IApplicationService>();
//create a mock Application Service to be used by business logic
var applicationDto = new Mock<ApplicationDto>();
//How do i set this up
applicationService.Setup(x => x.GetApplications()).Returns(IEnumerable<applicationDto.Object>);
}
我个人确信我需要测试业务逻辑而不是模拟它。那么,我究竟需要做什么来测试我的 ApplicationService
,然后将 Entity Framework 排除在外。
顺便说一下 ApplicationService
,它使用带有 NInject 的构造函数注入(inject)。那么用 NInject.MockingKernel
模拟它会设置依赖链吗?
最佳答案
在单元测试中使用依赖注入(inject) (IOC) 容器几乎没有或根本没有好处。依赖注入(inject)可帮助您创建松散耦合的组件,而松散耦合的组件更易于测试,仅此而已。
因此,如果你想测试一些服务,只需创建它的依赖关系的模型并将它们像往常一样传递给该服务(这里不需要涉及 IOC 容器,我很难想象,你将需要 IOC 容器的一些特性 -如上下文绑定(bind)、拦截等 - 在单元测试中)。
如果您希望您的 ApplicationService
易于测试,它应该看起来更像:
public class ApplicationService: IApplicationService
{
private readonly IEntityFrameworkRepo repo;
// dependency passed by constructor
public ApplicationService(IEntityFrameworkRepo repo)
{
this.repo = repo;
}
// save to db when DTO is eligible
public ApplicationDto Add(ApplicationDto dto)
{
// some business rule
if(dto.Id > 0 && dto.Name.Contains(string.Empty)){
//add to dbcontext and commit
}else{
throw new NotEligibleException();
}
}
}
此处依赖项由构造函数传递。在您的应用程序代码中,您将使用它与 IOC 容器一起进行构造函数注入(inject)(IOC 容器将负责创建 IEntityFrameworkRepo
的实例)。
但是在单元测试中,您可以只传递您自己创建的 IEntityFrameworkRepo
的某些实现的实例。
ApplicationDto
只要 ApplicationDto
是一些可以手动创建的对象,我就可以直接在单元测试中使用它(手动创建实例)。否则,我将不得不通过 IApplicationDto
之类的接口(interface)包装它,以便能够使用 Moq 对其进行模拟。
public class ApplicationDto{
public int Id {get; set;}
public string Name {get; set;}
}
这是单元测试的样子:
在单元测试中,我将使用 IApplicationRepo
的模拟实现,因为我不想配置,例如数据库连接、Web 服务等,我的主要目的是测试 ApplicationService
而不是底层存储库。另一个优点是测试无需针对各种机器进行特定配置即可运行。要模拟一些数据库存储库,我可以使用例如列表
。
[Test(Description = "Test If can successfully add application")]
public void CanAddApplicationIfEligible()
{
var repo = GetRepo();
var appService = new ApplicationService(repo);
var testAppDto = new ApplicationDto() { Id = 155, Name = "My Name" };
var currentItems = repo.ApplicationDtos.Count();
appService.Add(testAppDto);
Assert.AreEqual(currentItems + 1, repo.ApplicationDtos.Count());
var justAdded = repo.ApplicationsDto.Where(x=> x.Id = 155).FirstOrDefault();
Assert.IsNotNull(justAdded);
///....
}
private static IEntityFrameworkRepo GetRepo{
// create a mock repository
var listRepo = new List<ApplicationDto>{
new ApplicationDto {Id=1, Name="MyName"}
};
var repo = new Mock<IEntityFrameworkRepo>();
// setup the methods you know you will need for testing
// returns initialzed list instead of DB queryable like in real impl.
repo.Setup(x => x.ApplicationDtos)
.Returns<IQueryable<ApplicationDto>>(x=> listRepo);
// adds an instance of ApplicationDto to list
repo.Setup(x => x.Add(It.IsAny<ApplicationDto>())
.Callback<ApplicationDto>(a=> listRepo.Add(a));
return repo.Object;
}
注意:
已经发布了一个 ninject.mockingkernel延期。 wiki 上示例中描述的方法可以使您的单元测试代码更整洁,但那里描述的方法绝对不是依赖注入(inject)(它是服务定位器)。
关于c# - 我如何使用 Moq 或 NInject 模拟内核模拟接口(interface),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14648863/