如何构建单元工作/服务层/存储库,以便它们与DI(Unity)和Moq一起用于单元测试

jag*_*jag 5 entity-framework moq unity-container unit-of-work repository-pattern

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

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

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

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

我认为这是由于我试图将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);
    }
}
Run Code Online (Sandbox Code Playgroud)

服务层

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

Unity容器实例设置

    Instance.RegisterType<IService, Service>(new PerThreadLifetimeManager())
            .RegisterType<IUnitOfWork, UnitOfWork>();
Run Code Online (Sandbox Code Playgroud)

控制器构造函数

public GameController(IService service)
    {
        _service = service;
    }
Run Code Online (Sandbox Code Playgroud)

测试构造函数

_mockUnitOfWork = new Mock<IUnitOfWork>();
_mockServiceLayer = new Mock<IService>(_mockUnitOfWork.Object); //this line fails
Run Code Online (Sandbox Code Playgroud)

测试控制器方法

GameController Controller = new GameController(_mockServiceLayer.Object);
Run Code Online (Sandbox Code Playgroud)

Dan*_*.G. 1

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

_mockServiceLayer = new Mock<IService>();
_controller = new GameController(_mockServiceLayer.Object);
Run Code Online (Sandbox Code Playgroud)

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

_mockServiceLayer.Verify(s=> s.Save(), Times.Once()); 
Run Code Online (Sandbox Code Playgroud)

您会发现的问题是您的服务类没有从存储库中抽象控制器,因为您的控制器将通过属性获取存储库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"
Run Code Online (Sandbox Code Playgroud)

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

另一种方法是,如果您的服务类包含更高级别的方法,例如GetUserCreateUserAddUserToTeam(可能具有多个具有紧密相关方法的服务)。然后,该服务将阻止控制器检索数据/向存储库发送数据以及使用工作单元。

这样在您的测试中您只需要模拟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);
Run Code Online (Sandbox Code Playgroud)

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