【问题标题】:Mocking an abstract class that inherits from IEnumerable<T>模拟继承自 IEnumerable<T> 的抽象类
【发布时间】:2018-01-25 16:15:08
【问题描述】:

我正在尝试从我正在使用的库中模拟一个抽象类。我没有访问源代码,只有反编译版本:

public abstract class Event : IEnumerable<Message>, IEnumerable
{
    protected Event();
    public abstract bool IsValid { get; }
    public IEnumerator<Message> GetEnumerator();
    public IEnumerable<Message> GetMessages();        
}

这个反编译的代码让我有点困惑。首先,冗余继承,也没有实现非抽象方法,例如GetEnumeratorIEnumerable.GetEnumerator()。但是它已经编译了,并且可以正常工作,所以我想这只是反编译的产物(如果这甚至是一件事?)

我已经尝试了以下模拟,它编译并运行而不会抛出异常。

public static Event GetMockedEvent()
{
    var mock = new Mock<Event>();
    mock.Setup(e => e.IsValid).Returns(true);
    mock.As<IEnumerable>().Setup(e => e.GetEnumerator()).Returns(MessageList());

    return mock.Object;
}

private static IEnumerator<Message> MessageList()
{
    yield return GetMockedMessage();
    yield return GetMockedMessage();
}

private static Message GetMockedMessage()
{
    var mock = new Mock<Message>();
    // Unimportant setups...        
    return mock.Object;
}

但是,我在以下方式测试的模拟对象中没有得到任何元素

var ev = GetMockedEvent();
foreach (var msg in ev)
{
    //
}

但是枚举是空的,我不知道为什么。我已经为这个问题挠了一整天,所以我非常感谢你的帮助。

亲切的问候

【问题讨论】:

  • only the decompiled version: 这不是反编译版本,它只是该类型的公共 API,任何使用它的人都需要访问它。

标签: c# moq


【解决方案1】:

当您 foreach 处理一个序列时,IIRC 编译器会将其脱糖到对 GetEnumerator 的通用版本的调用,因此您必须模拟这个。

这样的事情可能会奏效,虽然我没有尝试过:

public static Event GetMockedEvent()
{
    var mock = new Mock<Event>();
    mock.Setup(e => e.IsValid).Returns(true);
    mock.As<IEnumerable<Message>>()
        .Setup(e => e.GetEnumerator())
        .Returns(() => MessageList());

    return mock.Object;
}

【讨论】:

  • 我添加了一个返回函数以允许多次调用。
  • 一开始我也想起了你说的,但实际上编译器会先寻找一个公开的合适的GetEnumerator方法,如果没有找到它就会搜索IEnumerable&lt;T&gt;接口。恕我直言,这不是很直观,但the spec 这么说。
  • 感谢您的回复,但我不明白。究竟哪种方法在这里被嘲笑?使用此代码 var evts = mock.Object 获得 2 个元素,但无法使用 foreach (var msg in evts) 对其进行迭代
  • @FstTesla 要么我读错了该规范,要么 C# 编译器不遵循规范。考虑这个例子,它表明 foreach 工作,即使非泛型实现抛出:gist.github.com/ploeh/a96c519cb4ec83ab96487b37c84e1e32
  • @MarkSeemann 我认为第一个 :) 您创建的示例与我阅读规范的行为完全相同,因为 IEnumerable&lt;T&gt; 的公共隐式实现“胜过”非通用实现。并非偶然,当您强制转换为非泛型时,不会选择公共 GetEnumerator
【解决方案2】:

前言 您粘贴的Event 类的代码只是元数据表示。如果你真的想查看它的源代码,请使用完整的反编译器,例如ILSpy (VS extension)。 结束前言

Event 类不能完全模拟,因为GetEnumerator 不是虚拟的(至少就您的 sn-p 而言),所以

  • 您必须按原样使用它的主体;和
  • 即使是模拟库也无法替代它。

由于该类隐式实现IEnumerable&lt;Message&gt;foreach 循环直接调用声明的方法,而不是您在GetMockedEvent 方法中设置的“显式实现”。

为了清楚起见,下面是我尝试运行的完整 sn-p。我决定抛出一个NotImplementedException 作为未知方法体的“中立”替代品。

void Main()
{
    var ev = GetMockedEvent();
    foreach (var msg in ev)
    {
        Console.WriteLine(msg);
    }
}

public abstract class Event : IEnumerable<Message>, IEnumerable
{
    protected Event() { }
    public abstract bool IsValid { get; }
    public IEnumerator<Message> GetEnumerator() { throw new NotImplementedException(); }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    public IEnumerable<Message> GetMessages() { throw new NotImplementedException(); }     
}

public class Message { }

public static Event GetMockedEvent()
{
    var mock = new Mock<Event>();
    mock.Setup(e => e.IsValid).Returns(true);
    mock.As<IEnumerable<Message>>().Setup(e => e.GetEnumerator()).Returns(MessageList());
    // The next line doesn't work either because the method is not virtual
    //mock.Setup(e => e.GetEnumerator()).Returns(MessageList());

    return mock.Object;
}

private static IEnumerator<Message> MessageList()
{
    yield return GetMockedMessage();
    yield return GetMockedMessage();
}

private static Message GetMockedMessage()
{
    var mock = new Mock<Message>();
    // Unimportant setups...
    return mock.Object;
}

【讨论】:

  • 感谢您的澄清,所以元数据表示就像 c++ 头文件?我想那没有什么我可以模拟的来使这项工作如我所愿?
  • 这些元数据已被 Visual Studio 提取(如果我错了,请纠正我)并表示类的公共方法的签名,只有很少的细节,当然也没有方法体。从元数据来看,你不能以一种有用的方式模拟这个类,因为它看起来既不是为了可扩展而设计的(没有虚拟GetEnumerator),也不是为了测试而模拟的(例如提供头接口)。跨度>
  • 一种解决方案可能是创建您自己的Event 子类以进行测试:毕竟,这是一个抽象类。也许元数据表示隐藏了一些你在实现子类时可能会发现的有用的东西。
  • 嗨,我试过了,但没有可以覆盖的虚拟方法。我使用 ILSpy 检查代码,IEnumerable.GetEnumerator()GetEnumerator()GetEnumerable()GetMessages() 在 Event 中都有非虚拟实现。最后他们都使用了一个抽象方法成员iterator(),但是它是一个内部成员,所以我不能覆盖它。这是反编译的代码:hastebin.com/raw/luguruhomi如果你想看看!
  • 我明白了。反编译的代码证实了我对这个类既不可外部扩展也不可模拟的第一个看法。
【解决方案3】:

Event 有三个公共成员(和一个显式实现的接口方法)。您已经模拟了其中的两个,然后编写了使用第三个的测试代码。如果您希望 GetEnumerator 实现在您的测试代码中返回某些内容,您需要实际模拟它(当然您也应该模拟非泛型版本,以防其他一些测试代码尝试使用它)。

【讨论】:

  • 更好的答案还可以解释如何模拟它们
  • @DaveM OP 从问题中表明他们知道如何做,他们只是没有对所有方法都这样做。
  • 您好,感谢您的回复。但由于这些方法不是虚拟的,我无法模拟它们。对吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-12
  • 1970-01-01
相关资源
最近更新 更多