我有一个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);
}
}
服务层
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);
答案 0 :(得分:1)
如果你想测试GameController
的方法,你只需要模拟/存根该类的依赖关系。就这样做:
_mockServiceLayer = new Mock<IService>();
_controller = new GameController(_mockServiceLayer.Object);
在测试Controller时,您不必担心服务的依赖性。 UnitOfWork永远不会暴露在您的服务之外,因此在测试控制器时不要担心它。在您的测试中,您现在可以设置对服务调用的方法的期望,例如验证Save被调用一次(如果您正在测试服务,那么您会担心IService.Save调用Save on a模拟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类实际上取决于您的存储库,而不仅仅取决于服务。
另一种方法是,如果您的服务类包含更高级别的方法,例如 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);
希望这有助于澄清一些事情!
答案 1 :(得分: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,每次模拟下面的图层。