【问题标题】:Mock object invoking original Action模拟对象调用原始动作
【发布时间】:2016-02-10 15:56:38
【问题描述】:

我总是尽量避免使用服务定位器,但在这种情况下,我有从 Base 继承并引发事件(例如 INotifyPropertyChanged)的模型,我希望始终在 UI 线程上调度这些事件。 我不能使用 DI 容器来保持模型构造函数为空。

在我的代码下面。

    public abstract class Base
    {
        private static IMainThreadDispatcherService _dispatcherService;

    /// <summary>
    /// The main thread dispatcher.
    /// </summary>
    protected static IMainThreadDispatcherService DispatcherService
    {
        get
        {
            return _dispatcherService ??
                   (_dispatcherService = DependencyLocatorService.Resolve<IMainThreadDispatcherService>());
        }
    }

    /// <summary>
    /// Helper method to raise PropertyChanged events.
    /// </summary>
    /// <typeparam name="T">Type of the property.</typeparam>
    /// <param name="selectorExpression">The expression to pass the property name.</param>
    public virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
        {
            throw new ArgumentNullException("selectorExpression");
        }

        MemberExpression body = selectorExpression.Body as MemberExpression;
        if (body == null)
        {
            throw new ArgumentException("The body must be a member expression");
        }

        DispatcherService.RequestMainThreadAction(() =>
        {
            NotifyPropertyChanged(selectorExpression);
        });
    }
}

/// <summary>
/// A service that holds on the UI thread dispatcher.
/// </summary>
public interface IMainThreadDispatcherService
{
    /// <summary>
    /// Invoke an action on main thread.
    /// </summary>
    /// <param name="action">The Action to invoke.</param>
    /// <returns>True if successfully invoked.</returns>
    bool RequestMainThreadAction(Action action);
}

/// <summary>
/// This class is an abstraction of the service locator.
/// </summary>
public class DependencyLocatorService : IDependencyLocatorService
{
    /// <summary>
    /// Constructs a new object instance injecting all required dependencies.
    /// </summary>
    /// <typeparam name="T">Type of.</typeparam>
    /// <returns>The object instance constructed.</returns>
    public T IocConstruct<T>()
    {
        // A resolver
    }

    /// <summary>
    /// Resolves an instance of the specified type.
    /// </summary>
    /// <typeparam name="T">Type of.</typeparam>
    /// <returns>The object instance.</returns>
    public static T Resolve<T>() where T : class
    {
        try
        {
            // A resolver
        }
        catch(Exception)
        {}
        return null;
    }

    /// <summary>
    /// Registers a singleton instance of type T.
    /// </summary>
    /// <typeparam name="T">The type to register.</typeparam>
    /// <param name="instance">The instance.</param>
    public static void RegisterSingleton<T>(T instance) where T : class
    {
        try
        {
            // A resolver
        }
        catch (Exception)
        {
        }
    }
}

问题是当我想对继承自 Base 的模型进行单元测试时,事情开始变得困难。

我正在寻求更改架构以启用正确的单元测试并模拟方法DispatcherService.RequestMainThreadAction,但仍会引发事件。 我正在使用 Moq 框架,但不确定是否可以以某种方式设置这种模拟,因为我希望调用原始 Action。

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    您可以尝试在 Moq 设置中使用 Callback 方法并调用其中的操作吗?

    【讨论】:

    • 拜托,如果您的日程安排允许,您可以举个例子让我理解。
    • 我现在在我的手机上,但你可以看看这个页面:github.com/Moq/moq4/wiki/Quickstart 或者这个也是stackoverflow.com/questions/2494930/…。基本上,当您设置要模拟的方法时,您会执行 .Callback((Action a) => a());这意味着您传入的操作会运行。有意义吗?
    • 是的,这是有道理的,但我可能不明白,因为这不是我需要的。我想要的是模拟依赖项,IMainThreadDispatcherService 属性,但仍然在 SUT 中,它是任何 Base 派生类,仍然运行原始 Action,NotifyPropertyChanged(selectorExpression)。
    • 如果我正确理解了问题,您仍然希望运行传递给 DispatcherService 的 NotifyPropertyChanged 作为模拟。如果在您设置 IMainThreadDispatcherService 方法时是这种情况,您可以为该方法设置回调并告诉模拟运行您传递的操作。在这种情况下,您将运行 NotifyPropertyChanged,因为您将模拟配置为具有某些行为。
    • 两种解决方案都可以正常工作,我选择了这个,因为我可以使用 Mock。我最初的问题是用于测试的 MvvmCross 设置(我不喜欢它,但是您使用继承的遗留代码)。然后一切都很完美。
    【解决方案2】:

    除非您确实需要Mock,否则为什么不使用简单的存根?

    public class DispatcherServiceStub : IMainThreadDispatcherService 
    {
      public bool RequestMainThreadAction(Action action)
      {
        action();
      }
    }
    

    【讨论】:

    • 是的,如果我妥协并公开 DispatcherService 属性并将其设置为单元测试设置中的存根,我现在不在机器上,明天将进行测试。我没有看到另一种保持属性只读和受保护的方法。
    • @GeorgeTaskos 您不需要公开属性,因为您正在通过DependencyLocatorService 解析值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-07
    • 1970-01-01
    相关资源
    最近更新 更多