Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow hidden methods to be mocked #22

Closed
tkellogg opened this issue Jul 17, 2012 · 8 comments
Closed

Allow hidden methods to be mocked #22

tkellogg opened this issue Jul 17, 2012 · 8 comments

Comments

@tkellogg
Copy link
Collaborator

The primary example of this is mocking IEnumerable:

interface IMyCollection : IEnumerable<Item>
{
}

The IEnumerable<Item>.GetEnumerator() actually hides IEnumerator.GetEnumerator(). As a result, this setup code only sets up the generic version

var mock = new Mock<IMyCollection>();
mock.Setup(x => x.GetEnumerator()).Returns(list.GetEnumerator())

When you use Linq methods, like mock.Object.FirstOrDefault(), it tries to use the IEnumerable.GetEnumerator() method. We need a way to conveniently do this setup.

I propose that we also setup all hidden methods/properties by default. So if you do that mock.Setup(x => x.GetEnumerator()), it just works.

I also propose that we support a cast in the Setup expression, mock.Setup(x => ((IEnumerable)x).GetEnumerator()) to be specific about which method we're talking about.

@sloncho
Copy link

sloncho commented May 29, 2013

Interesting, this works. What do I miss?

        public interface IMyCollection : IEnumerable<int>
        {

        }

        [Test]
        public void TEST()
        {
            //arrange
            var list = new List<int> {1, 2, 3};
            var mock = new Mock<IMyCollection>(MockBehavior.Strict);
            mock.Setup(m => m.GetEnumerator()).Returns(list.GetEnumerator());

            //act
            var first = mock.Object.First();

            //assert
            first.Should().Be(1);
        }

@sloncho
Copy link

sloncho commented May 29, 2013

Also, there is already a mechanism to get another interface and set it:

var mock = new Mock<IMyCollection>(MockBehavior.Strict);
var ienum = mock.As<IEnumerable>();
ienum.Setup(e => e.GetEnumerator()).Returns(list.GetEnumerator());

@rubberduck203
Copy link

Real world example of when some syntactic sugar would be nice.

        var refs = new Mock<References>();
        refs.Setup(r => r.GetEnumerator()).Returns(ReferenceList());
        refs.As<IEnumerable>().Setup(r => r.GetEnumerator()).Returns(ReferenceList);

This is a situation where to properly set up the mock, we need to repeat ourselves and provide the same implementation twice.

@stakx
Copy link
Contributor

stakx commented Jun 16, 2017

@tkellogg, @rubberduck203: Do you still wish to follow up on this, or can we close this? I guess the original question has been answered?

@rubberduck203: When you implement IEnumerable<T> manually, you will also have to provide separate implementations for IEnumerable.GetEnumerator and IEnumerable<T>.GetEnumerator. Simply because they are different methods with differing signatures. I don't see any generally useful syntactic sugar that would allow you to set up unrelated methods in one go (apart from something really coarse-grained like mock.SetupEverything()). What kind of syntactic sugar did you have in mind?

In my opinion, the problem here does not lie with Moq missing something, but with the .NET Framework having something too much. IEnumerable has been mostly a useless PITA ever since the generic IEnumerable<T> was introduced in .NET 2.0.

@rubberduck203
Copy link

@stakx I don't know how now works under the hood. If you believe it would be a burden to create & maintain then I would close this. It's not a terrible burden to setup both methods once you understand it. When I originally faced this issue I spent 6 hours in a state of utter confusion though. I believe this could be solved with an update to the documentation.

@stakx
Copy link
Contributor

stakx commented Jun 16, 2017

Fair enough. So basically, what you are saying is that .As<TInterface>() is not easy enough to discover if you don't know it already? I suppose in that case, an update to the documentation would seem a good idea.

I am not against an improvement, I was just under the false impression that this issue was specifically about IEnumerable and IEnumerable<T> and I didn't see a general solution.

If you have any suggestions what kind of new syntax could help this, let's discuss it. ;)

@rubberduck203
Copy link

IEnumerable & IEnumerable were just my specific use case (although, likely the most likely one). I could see a default behavior (as a user) of setting up both the visible & hidden method on a single call, but understand if it's not reasonable to implement. I'll add something to the QuickStart. You can close this as far as I'm concerned.

@stakx
Copy link
Contributor

stakx commented Jun 21, 2017

I have some trouble understanding what exactly is being asked here. The original code example probably isn't ideal because, like @sloncho already pointed out, the shown code actually works.

@tkellogg: I'll try to rephrase your issue in more general terms. Is this what you meant?

public interface IA
{
    int GetValue();
}

public interface IB : IA
{
    new int GetValue();
}

[Fact]
public static void ShadowedMembersAreSetUpLikeShadowingMember()
{
    const int expected = 42;

    var mock = new Mock<IB>();
    mock.Setup(_ => _.GetValue()).Returns(expected);

    var b = (IB)mock.Object;
    Assert.Equal(expected, actual: b.GetValue());  // => passes

    var a = (IA)mock.Object;
    Assert.Equal(expected, actual: a.GetValue());  // => fails!
}

And, if this is the problem we're talking about, are you asking that shadowed members (like A.GetValue) should work in the same way as the members shadowing them (like B.GetValue) — unless of course when they are explicitly set up via mock.As<IA>().Setup(...)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants