Подтвердить что ты не робот

Как скомпоновать (с помощью 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...

4b9b3361

Ответ 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();
  }
}