【问题标题】:How to structure unitofwork / service layer / repository, so that they work with DI (Unity) and Moq for Unit Testing如何构建工作单元/服务层/存储库,以便它们与 DI (Unity) 和 Moq 一起进行单元测试
【发布时间】:2014-02-12 09:43:56
【问题描述】:

我有一个 MVC 应用程序(EF6,SQL Server CE 4),我最近对其进行了重构,添加了一个 UnitOfWork 类和一个服务层(这样我就可以在每个请求中使用一个 DbContext,并成功地进行事务)。

以前,我使用 Unity 将存储库注入控制器。我的单元测试(针对控制器)很容易设置 - 我只是模拟了每个存储库,并将它们传递给控制器​​构造函数。

重构后,我现在使用 Unity 将服务层(到控制器)和UnitOfWork(到服务层)注入。服务层现在通过将UnitOfWork.DbContext 传递给存储库的构造函数来实例化每个存储库。

在我的单元测试中,我试图模拟UnitOfWork 和ServiceLayer(并将模拟的UnitOfWork 对象传递给ServiceLayer 的构造函数)。但是,测试失败,说“TestFixtureSetup 在 ControllerTest 中失败”。

我认为这是由于我试图将 UnitOfWork 模拟传递到 ServiceLayer 模拟,因此希望能提供有关如何正确执行此操作的任何指导。

相关代码sn-ps如下。

工作单位

public interface IUnitOfWork:IDisposable
{
    void Save();
    IDSMContext Context { get; }
}

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private IDSMContext _context;

    public UnitOfWork()
    {
       _context = new IDSMContext();
    }

    public IDSMContext Context
    {
        get {return _context;}
    }

    public void Save()
    {
        _context.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

服务层

public interface IService
{
    // Repositories
    IUserRepository Users { get; }
    IUserTeamRepository UserTeams { get; }
    IPlayerRepository Players { get; }
    IGameRepository Games { get; }
    IUserTeam_PlayerRepository UserTeamPlayers { get; }

    void Save();
}

public class Service: IService, IDisposable
{
    private IUnitOfWork _unitOfWork;
    private IUserRepository _userRepository;
    private IUserTeamRepository _userTeamRepository;
    private IPlayerRepository _playerRepository;
    private IGameRepository _gameRepository;
    private IUserTeam_PlayerRepository _userTeamPlayerRepository;

    public Service(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
        initialiseRepos();
    }

    private void initialiseRepos(){
        _userRepository = _userRepository ?? new UserRepository(_unitOfWork.Context);
        _userTeamRepository = _userTeamRepository ?? new UserTeamRepository(_unitOfWork.Context);
        _playerRepository = _playerRepository ?? new PlayerRepository(_unitOfWork.Context);
        _gameRepository = _gameRepository ?? new GameRepository(_unitOfWork.Context);
        _userTeamPlayerRepository = _userTeamPlayerRepository ?? new UserTeam_PlayerRepository(_unitOfWork.Context);
    }

    public IUserRepository Users { get { return _userRepository; } }
    public IUserTeamRepository UserTeams { get { return _userTeamRepository; } }
    public IPlayerRepository Players { get { return _playerRepository; } }
    public IGameRepository Games { get { return _gameRepository; } }
    public IUserTeam_PlayerRepository UserTeamPlayers { get { return _userTeamPlayerRepository; } }

    public void Save()
    {
        _unitOfWork.Save();
    }

Unity 容器实例设置

    Instance.RegisterType<IService, Service>(new PerThreadLifetimeManager())
            .RegisterType<IUnitOfWork, UnitOfWork>();

控制器构造函数

public GameController(IService service)
    {
        _service = service;
    }

测试构造函数

_mockUnitOfWork = new Mock<IUnitOfWork>();
_mockServiceLayer = new Mock<IService>(_mockUnitOfWork.Object); //this line fails

测试控制器方法

GameController Controller = new GameController(_mockServiceLayer.Object);

【问题讨论】:

    标签: entity-framework unity-container moq repository-pattern unit-of-work


    【解决方案1】:

    如果您想测试GameController 的方法,您只需要模拟/存根该类的依赖项。只需这样做:

    _mockServiceLayer = new Mock<IService>();
    _controller = new GameController(_mockServiceLayer.Object);
    

    当您测试控制器时,您不应该担心服务的依赖关系。 UnitOfWork 永远不会暴露在您的服务之外,因此在测试控制器时不要担心它。在您的测试中,您现在可以设置对 服务 调用方法的预期,例如验证 Save 是否被调用过一次(如果您正在测试服务,那么您会担心 IService.Save 在IUnitOfWork 的模拟!):

    _mockServiceLayer.Verify(s=> s.Save(), Times.Once()); 
    

    您会发现的问题是您的服务类没有从存储库中抽象出控制器,因为您的控制器将通过IService 中的属性获取存储库并直接查询存储库。因此,如果您想测试您的控制器方法,您仍然需要模拟存储库,执行以下操作:

    //Initialization before each test:
    _mockUserRepo = new Mock<IUserRepository>();
    //...other repositories
    _mockServiceLayer = new Mock<IService>();
    _mockServiceLayer.Setup(s => s.Users).Returns(_mockUserRepo.Object);
    //... setup properties in IService for other repositories
    _controller = new GameController(_mockServiceLayer.Object);
    
    //In some test:
    var user = new User();    
    _mockUserRepo.Setup(s => s.Get(123)).Returns(user);
    
    call some controller method and make sure returned model is "user"
    

    这样您可能会发现自己配置了一些存储库和 UnityOfWork 返回的期望和数据,只是为了测试 Controller 中的方法!更不用说您的 Controller 类实际上取决于您的存储库,而不仅仅是服务。

    另一种方法是,如果您的服务类包含更高级别的方法,例如 GetUserCreateUserAddUserToTeam(可能有多个服务密切相关方法)。然后,该服务将屏蔽控制器检索/发送数据到存储库和使用 UnitOfWork。

    这样在你的测试中你只需要模拟IService。 例如,典型“GET”操作的测试可能如下所示:

    //Arrange
    var user = new User();    
    _mockServiceLayer.Setup(s => s.GetUser(123)).Returns(user);
    
    //Act
    var viewResult = _controller.GetUser(123) as ViewResult;
    
    //Assert
    Assert.AreEqual(user, viewResult.Model);
    

    希望这将有助于澄清一些事情!

    【讨论】:

    • 我一直在尝试了解使用服务层的最佳方式 - 我想到了我最好将所有存储库方法移到服务层中,正如你所建议的那样(我只是还没有解决这个问题,因为我偶然发现的第一个问题是单元测试不起作用)。一个问题是服务层现在将包含大量的方法。您是否建议拥有多个服务层?
    • 我会将相关方法分组到不同的服务中。例如,您最终可能会使用 UserService、TeamService 和 PlayerService。所以你会有一个由几个服务接口/类组成的服务层。您可能已经使用 Controller 类进行了类似的分组
    【解决方案2】:

    在失败的行中,您正在模拟没有构造函数的 IService,因此传递它 args 将导致它失败。由于您只是尝试对控制器进行单元测试,因此您应该将行更改为:

    _mockServiceLayer = new Mock<IService>();
    

    然后使用_mockServiceLayer.Setup(...) 指定您想要的行为。请记住,您的界面对您的工作单元一无所知,因此您无需模拟工作单元。

    如果您真的想同时测试控制器和服务层,那么您可以这样做:

    _mockUnitOfWork = new Mock<IUnitOfWork>();
    var serviceLayer = new Service(_mockUnitOfWork.Object);
    var controller = new GameController(serviceLayer);
    

    您最好分别对控制器和 serviceLayer 进行单元测试,每次都模拟下面的层。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多