如何使用Moq或NInject Mocking Kernel模拟接口

Dee*_*ons 4 c# nunit unit-testing ninject moq

我只是讨论了关于模拟和依赖注入的问题和博客.得出结论我只需要模拟客户端使用的接口.我期待在这里测试一个简单的用例,不知道.

合约

public Interface IApplicationService
{

     bool DeleteApplication(int id);

     ApplicationDto AddApplication(ApplicationDto application);

     IEnumerable<ApplicationDto> GetApplications();

}
Run Code Online (Sandbox Code Playgroud)

实施(我要去模拟)

public Class ApplicationService:IApplicationService
{
    private EntityFrameworkRepo repo; 

    public ApplicationService()
    {
         repo = new EntityFrameworkRepo();
    }

    public ApplicationDto Add(ApplicationDto dto)
    {
         //add to dbcontext and commit
    }

}
Run Code Online (Sandbox Code Playgroud)

模拟代码

[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>);
}
Run Code Online (Sandbox Code Playgroud)

我确信我需要测试业务逻辑而不是嘲笑它.那么我必须做些什么才能测试我ApplicationService,然后保持实体框架.

顺便ApplicationService提一下,它使用NInject构造函数注入.那么嘲笑这个NInject.MockingKernel将设置依赖链?

mip*_*e34 7

在单元测试中使用依赖注入很少或没有好处.依赖注入有助于您创建松散耦合组件,松散耦合组件更容易测试,就是这样.

因此,如果你想测试一些服务,只需创建它的依赖项的模型并像往常一样将它们传递给该服务(这里不需要涉及IOC容器,我几乎无法想象,你需要一些IOC容器的功能 - 比如上下文绑定,拦截等 - 内部单元测试).

如果您希望自己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();
         } 
    }   
}
Run Code Online (Sandbox Code Playgroud)

这里依赖项由构造函数传递.在您的应用程序代码中,您将与IOC容器一起使用它来进行构造函数注入(IOC容器将负责创建实例IEntityFrameworkRepo).

但是在单元测试中,你可以只传递IEntityFrameworkRepo自己创建的一些实现的实例.

ApplicationDto

只要ApplicationDto是可以通过手工创建的某个对象,我就可以直接在单元测试中使用它(手动创建实例).否则我将不得不通过接口包装它IApplicationDto,以便能够用Moq模拟它.

public class ApplicationDto{
     public int Id {get; set;}
     public string Name {get; set;}
}
Run Code Online (Sandbox Code Playgroud)

以下是单元测试的样子:

在单元测试中,我将使用模拟实现IApplicationRepo,因为我不想配置例如数据库连接,Web服务等,我的主要目的是测试ApplicationService不是底层存储库.另一个优点是测试可以在没有针对各种机器的特定配置的情况下运行.要模拟一些数据库存储库,我可以使用例如List.

[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;
}
Run Code Online (Sandbox Code Playgroud)

注意:

已经发布了一个ninject.mockingkernel扩展.wiki上的示例中描述的方法可以使您的单元测试代码更加整洁,但是那里描述的方法肯定不是依赖注入(它是服务定位器).

  • *“无需使用依赖注入 (...)”* - DI 不是您打开或关闭的东西。您要么有支持它的类设计,要么没有。在单元测试中使用 DI 容器没有多大意义(如您所言),但 DI 本身是抽象概念,与任何容器无关(必然)。 (2认同)