5

我有一个MVC应用程序(EF6,SQL Server CE 4),我最近重构了一个UnitOfWork类和一个服务层(以便我可以利用每个请求一个单独的DbContext,并成功进行交易)。如何构建单元工作/服务层/存储库,以便它们与DI(Unity)和Moq一起工作用于单元测试

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

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

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

我认为这是由于我试图通过UnitOfWork模拟进入ServiceLayer模拟,所以将不胜感激关于如何正确执行此操作的任何指导。

下面的相关代码片段。

的UnitOfWork

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(); 
    } 

统一容器实例设置

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); 

回答

1

如果你想测试你只需要模拟/存根GameController方法该类的依赖关系。只需执行以下操作:

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

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

_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); 

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

+0

我一直在试图理解使用服务层的最佳方式 - 它已经发生在我身上了,我最好将所有的存储库方法移动到服务层中,就像你建议的那样(我刚刚没有得到但是,因为我偶然发现的第一个问题是单元测试不起作用)。与此相关的一个问题是,服务层现在将包含很多和很多方法。你会建议有多个服务层? – jag

+0

我会将相关的方法分组到不同的服务中。例如,您最终可能会使用UserService,TeamService和PlayerService。所以你会有一个由几个服务接口/类组成的_service layer_。您可能已经拥有与Controller类相似的分组 –

0

在失败的行中,您正在嘲笑没有构造函数的IService,因此传递参数会导致失败。既然你只是想单元测试控制器,你应该行改成这样:

_mockServiceLayer = new Mock<IService>(); 

,然后指定你想使用_mockServiceLayer.Setup(...)行为。记住你的界面并不知道你的工作单元,所以你不需要模拟工作单元。

如果你真的想一起测试控制器和服务层,那么你会做这样的事情:

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

你可能会更好单元单独测试控制器和serviceLayer,每次嘲讽下面的图层。

相关问题