简体   繁体   中英

Mocking an abstract class that inherits from IEnumerable<T>

I'm trying to mock an abstract class from a library that I'm using. I don't have access to the source code, only the decompiled version:

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

This decompiled code confuses me slighty. First, the redundant inheritance, and also there's no implementation of non-abstract methods eg GetEnumerator or IEnumerable.GetEnumerator() . But it has compiled, and it works so I suppose it's just an artefact of the decompilation (if that is even a thing?)

I have tried the following mock, which compiles and runs without throwing exceptions.

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

However I don't get any elements in the mocked object which I test in the following way

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

But the enumeration is empty, and I cannot figure out why. I have scratched my head with this issue for a full day now so I'd be very appreciative of your help.

Kind regards

When you foreach over a sequence, IIRC the compiler will desugar that to a call to the generic version of GetEnumerator , so that's the one you'll have to mock.

Something like this might do the trick, although I haven't tried:

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

Event has three public members (and one explicitly implemented interface method). You've mocked two of them, and then written test code that uses the third. You need to actually mock the GetEnumerator implementation if you want it to return something in your test code (and of course you should mock the non-generic version as well, in case some other test code tries to use it).

Foreword The code of the Event class that you pasted is only a metadata representation. If you really want to see its source code, use a full decompiler such as ILSpy ( VS extension ). End foreword

The Event class as-is can't be fully mocked, because the GetEnumerator is not virtual (at least as far as your snippet is telling), so

  • you must use its body as-is; and
  • not even a mocking libraries can replace it.

Since the class implements IEnumerable<Message> implicitly, the foreach loop calls directly the declared method, not the "explicit implementation" that you setup in the GetMockedEvent method.

To be clear, below is the full snippet that I tried to run. I decided to throw a NotImplementedException as a "neutral" replacement for the unknown method bodies.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM