Как издеваться (с Moq) методами Unity - PullRequest
13 голосов
/ 25 сентября 2010

Методы расширения не подходят для тестирования (это описано здесь: Методы расширения Mocking с 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 ...

Ответы [ 4 ]

7 голосов
/ 23 января 2015

Я знаю, что опаздываю на вечеринку, но у меня возникла та же проблема, и я решил ее, смоделировав следующий метод -

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);

На всякий случай, если кому-то нужно это смоделировать и не может удалить зависимость от контейнера.

6 голосов
/ 25 сентября 2010

Удалите зависимость от IUnityContainer, и все станет намного проще и чище. Вместо этого позвольте единице вводить ваши зависимости, которые абстрагируются в интерфейсы. Это легко поиздеваться. Вот пример использования небольшого трюка с Unity, который внедряет авто-фабрику для 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)
   {
       ...
   }
}
4 голосов
/ 25 сентября 2010

Думаю, я нашел наиболее подходящее решение для теста: нет необходимости проверять единичный контейнер и проверять, был ли из него взят объект 'log'. Я просто сделаю макет для объекта '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"
            );

Спасибо.

1 голос
/ 27 сентября 2010

Мне придется не соглашаться с обоими ответами.TheCodeKing, вполне законно использовать интерфейс IoC напрямую.Одним из примеров может быть фабрика контроллеров в проекте ASP.NET - где можно выполнить нетривиальное разрешение, используя несколько методов на интерфейсе IUnityContainer.

Хмм ... с автоматическим вводом labdas,никогда не нужно тестировать интерфейс IoC напрямую.Вы можете просто передать лямбду и убедиться, что она вызывается с правильными параметрами.

Будда, вы должны никогда ввести контейнер IoC в свой модульный тест.Зависимости должны быть введены вручную.

Ниже приведено решение, которое я использую.Я в основном создал слой абстракции над 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...
    }
}

И переписайте модульный тест, какитак:

[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();
  }
}
...