Как скомпоновать (с помощью Moq) методы Unity
Методы расширения не подходят для тестирования (описанного здесь: Mocking Extension Methods with Moq, http://www.clariusconsulting.net/blogs/kzu/archive/2009/12/22/Howtomockextensionmethods.aspx).
Но, возможно, есть некоторые решения для насмешек методов Unity? В моем случае у меня есть следующая функция:
public class MyManager
{
public MyManager(IUnityContainer container) : base(container) { }
public IResult DoJob(IData data)
{
IMyLog log = MyContainer.Resolve<IMyLog>();
... use log.Id ...
MyContainer.Resolve<...>();//usage for other purposes...
}
Я хочу быть уверенным, что метод DoJob всегда будет получать объект "IMyLog" из контейнера, но не из других источников... как я могу проверить это?
Моя первоначальная идея заключалась в том, чтобы изменить реализацию метода DoJob и использовать его:
IMyLog log = UnityContainer.Resolve(typeof(IMyLog)) as IMyLog;
Но "Resolve (Type t,...)" также является методом расширения...
Любые мысли приветствуются.
P.S. Обратите внимание, что объект "мой журнал" создается далеко от MyManager.DoJob...
Ответы
Ответ 1
Угадайте, я нашел наиболее подходящее решение для теста: нет необходимости издеваться над контейнером единицу и проверять, был ли из него выведен "лог". Я просто сделаю mock для объекта "Log", зарегистрирую экземпляр объекта в контейнере и проверит тест, если этот объект журнала действительно используется.
Это сделает то, что требуется.
Mock<IMyLog> mockLog = new Mock<IMyLog>();
mockLog.Setup(mock=>mock.Id).Returns(TestLogId);
IUnityContainer container = new UnityContainer();
container
.RegisterInstance(mockCommandExecutionLog.Object)
...
;
...
mockLog.Verify(
mock => mock.Id,
Times.Once(),
"It seems like 'Log' object is not used"
);
Спасибо.
Ответ 2
Удалите зависимость от IUnityContainer, и все станет намного проще и чище. Вместо этого пусть единица вводит ваши зависимости, которые абстрагируются на интерфейсы. Они легко насмехаются. Вот пример использования небольшого трюка с Unity, который вводит auto- factory для IMyLog.
public class MyManager
{
private readonly Func<IMyLog> logFactory;
public MyManager(Func<IMyLog> logFactory)
{
this.logFactory = logFactory;
}
public IResult DoJob(IData data)
{
IMyLog log = logFactory();
...
}
}
Или если вам не нужно создавать экземпляр каждый раз:
public class MyManager
{
private readonly IMyLog myLog;
public MyManager(IMyLog myLog)
{
this.myLog = myLog;
}
public IResult DoJob(IData data)
{
...
}
}
Ответ 3
Я знаю, что опаздываю на вечеринку, но у меня была такая же проблема, и разрешил ее, издеваясь над следующим методом -
IUnityContainer.Resolve(Type t, string name, params ResolverOverride[] resolverOverrides);
Например -
unityMock = new Mock<IUnityContainer>(MockBehavior.Strict);
validatorMock = new Mock<IValidator>(MockBehavior.Strict);
unityMock.Setup(p => p.Resolve(typeof(IValidator), null))
.Returns(validatorMock.Object);
На всякий случай кто-то должен издеваться над этим и не может удалить зависимость от контейнера.
Ответ 4
Мне придется не согласиться с обоими ответами. TheCodeKing, полностью законно использовать интерфейс IoC напрямую. Одним из примеров может быть контроллер factory в проекте ASP.NET, где можно сделать нетривиальное разрешение, используя несколько методов на интерфейсе IUnityContainer
.
Hmm... с автоматическим factory инъекционным labdas, никогда не нужно unit test интерфейс IoC напрямую. Вы можете просто передать лямбду и убедиться, что она вызвана с правильными параметрами.
Будда, вы должны никогда принести контейнер IoC в ваш unit test. Зависимости должны вводиться вручную.
Ниже приводится решение, которое я использую. Я в основном создал слой абстракции над IUnityContainer
и реализовал простой класс, который делегирует IUnityContainer
. Поскольку мой интерфейс не содержит методов расширения, я могу легко издеваться над ним.
public interface IDIContainer {
void RegisterType<TFrom>() where TFrom : class;
void RegisterType<TFrom, TTo>() where TTo : TFrom;
void RegisterType<TFrom, TTo>(string name) where TTo : TFrom;
void RegisterType(Type from, Type to);
void RegisterType(Type from, Type to, string name);
void RegisterInstance<TFrom>(TFrom instance) where TFrom : class;
T Resolve<T>();
T Resolve<T>(string name);
IEnumerable<T> ResolveAll<T>();
bool IsRegistered<TFrom>(string name) where TFrom : class;
bool IsRegistered<TFrom>() where TFrom : class;
}
public class DIContainer : IDIContainer {
IUnityContainer m_Container = new UnityContainer();
#region IDIContainer Members
public void RegisterType<TFrom>() where TFrom : class {
m_Container.RegisterType<TFrom>();
}
public void RegisterType<TFrom, TTo>() where TTo : TFrom {
m_Container.RegisterType<TFrom, TTo>();
}
public void RegisterType<TFrom, TTo>(string name) where TTo : TFrom {
m_Container.RegisterType<TFrom, TTo>(name);
}
public void RegisterType(Type from, Type to) {
m_Container.RegisterType(from, to);
}
public void RegisterType(Type from, Type to, string name) {
m_Container.RegisterType(from, to, name);
}
public void RegisterInstance<TFrom>(TFrom instance) where TFrom : class {
m_Container.RegisterInstance<TFrom>(instance);
}
public T Resolve<T>() {
return m_Container.Resolve<T>();
}
public IEnumerable<T> ResolveAll<T>() {
return m_Container.ResolveAll<T>();
}
public T Resolve<T>(string name) {
return m_Container.Resolve<T>(name);
}
public bool IsRegistered<TFrom>(string name) where TFrom : class {
return m_Container.IsRegistered<TFrom>(name);
}
public bool IsRegistered<TFrom>() where TFrom : class {
return m_Container.IsRegistered<TFrom>();
}
#endregion
}
Теперь перепишем ваш класс для использования IDIContainer
:
public class MyManager
{
public MyManager(IDIContainer container) : base(container) { }
public IResult DoJob(IData data)
{
IMyLog log = MyContainer.Resolve<IMyLog>();
... use log.Id ...
MyContainer.Resolve<...>();//usage for other purposes...
}
}
И перепишите unit test так:
[TestClass]
public class Test {
[TestMethod]
public void TestDoJob() {
Mock<IMyLog> mockLog = new Mock<IMyLog>();
Mock<IDIContainer> containerMock = new Mock<IDIContainer>();
//Setup mock container to return a log mock we set up earlier
containerMock.Setup(c=>c.Resolve<IMyLog>()),Returns(mockLog);
//Verify that all setups have been performed
containerMock.VerifyAll();
}
}