5

У меня есть приложение MVC (EF6, SQL Server CE 4), которое я недавно реорганизовал для добавления класса UnitOfWork и уровень обслуживания (чтобы я мог использовать один DbContext за запрос и успешно выполнять транзакции).Как структурировать unitofwork/сервисный уровень/репозиторий, чтобы они работали с 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); 
    } 
} 

Service Layer

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 Setup

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

Метод контроллера Конструктор

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

Test Constructor

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

Controller Test

GameController Controller = new GameController(_mockServiceLayer.Object); 

ответ

1

Если вы хотите проверить методы GameController вам просто нужно глумиться/окурок зависимости этого класса. Просто сделайте это:

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

При тестировании контроллера, вы не должны беспокоиться о зависимостях службы. UnitOfWork никогда не выставляется за пределами вашей службы, поэтому не беспокойтесь об этом при тестировании контроллера. На ваших тестах вы можете теперь установить ожидания методов, вызванных вашим сервисом, например, чтобы проверить, что Save был вызван один раз (если вы тестировали службу, тогда вы будете беспокоиться об IService.Save, вызывающем 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 эффективно зависит от ваших репозиториев, а не только от службы.

Другой подход был бы, если ваш класс сервиса содержит более высокие методы уровня, как GetUser, CreateUser или AddUserToTeam (вероятно наличие нескольких услуг с близкородственными методами). Затем служба будет защищать контроллер от получения/отправки данных в репозитории и использования 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

Я пытался понять, как использовать сервисный уровень - и это произошло со мной. Я бы лучше переместил все методы репозитория на уровень сервиса, как вы предлагаете (я просто не обходил к тому же, поскольку первая проблема, на которую я наткнулся, была неспособна работать с Unit Tests). Одна из проблем заключается в том, что уровень сервиса теперь будет содержать множество и множество методов. Вы предложили бы иметь несколько уровней обслуживания? – jag

+0

Я бы группировал связанные методы в разные службы. Вы можете в конечном итоге, например, с помощью UserService, TeamService и PlayerService. Таким образом, у вас будет слой _service_, состоящий из нескольких сервисных интерфейсов/классов. Вероятно, у вас уже есть аналогичная группировка с классами Controller –

0

В строке, которая не работает, вы издеваетесь над IService, у которого нет конструктора, поэтому передача его args приведет к сбою. Так как вы только пытаетесь модульного тестирования контроллера, вы должны изменить строку следующим образом:

_mockServiceLayer = new Mock<IService>(); 

, а затем указать поведение вы хотите с помощью _mockServiceLayer.Setup(...). Помните, что ваш интерфейс ничего не знает о вашей единице работы, поэтому вам не нужно издеваться над единицей работы.

Если вы действительно хотите, чтобы проверить контроллер и сервисный слой вместе, то вы могли бы сделать что-то вроде этого:

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

Вы, вероятно, будет лучше модульного тестирования контроллеров и serviceLayer отдельно, каждый раз, когда насмешливый слой ниже.

 Смежные вопросы

  • Нет связанных вопросов^_^