Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moq: Simple way to setup property that is part of multiple interfaces

Tags:

c#

moq

I have some interfaces which I'm trying to mock with the following structure (simplified):

interface A 
{
    DateTime DateCreated { get; set; }
}
interface B : A
{
    DateTime DateCreated { get; set; }
}

The problem I'm having is that DateCreated is a separate property for each interface, so even though the concrete objects that I'm aware of only have one shared implementation of these shared properties, calling Mock.SetupAllProperties gives each one a separate implementation. This means they do not share values, so calling ((B)obj).DateCreated = {blah} doesn't give the desired result elsewhere when ((A)obj).DateCreated is accessed.

The only way I see to solve this in Moq is to do the following:

var m = new Mock<B>();
DateTime closure;
m.SetupGet(x => x.DateCreated).Returns(() => closure);
m.SetupSet(x => x.DateCreated).Callback(value => { closure = value; });
m.As<A>.SetupGet(x => x.DateCreated).Returns(() => closure);
m.As<A>.SetupSet(x => x.DateCreated).Callback(value => { closure = value; });

This is tedious, error prone and I need to do this for at least a dozen properties and I don't know how many more. I could probably write a generic method to do this but it seems like there must be a simpler solution. Can anyone suggest a better way to do this?

(I'd love to "correct" the definitions of the interfaces, but this code in common legacy code shared throughout our organization. As this is a potentially breaking change, I can't just go around making modifications all willy-nilly.)

Edit: Just to clarify, what I want is a simple way to be able to do the equivalent of this with Moq:

class C : B
{
    public DateTime DateCreated { get; set; }
}

Since B and A both have a property with the same name and type, that single property serves both of them. It seems like since there's a simple way to do this in actual code there should be an equally simple way to do it with Moq.

like image 655
Erik Avatar asked Sep 29 '22 22:09

Erik


1 Answers

After re-reading your question (I must not be awake) I realize I missed your point entirely. I don't see an easy way to do what you want, but if you want to live dangerously you could abuse the bug I mentioned below by setting up only the base interface's property:

[Test]
public void Constructor_Always_Succeeds()
{
    var mockOfB = new Mock<B>();
    var mockOfA = mockOfB.As<A>();

    mockOfA.SetupProperty(p => p.DateCreated);

    B b = mockOfB.Object;
    A a = b;
    DateTime aTime = DateTime.Now;
    DateTime bTime = DateTime.Now.AddDays(-1);

    a.DateCreated = aTime;
    Assert.That(a.DateCreated, Is.EqualTo(aTime));
    Assert.That(b.DateCreated, Is.EqualTo(aTime));

    b.DateCreated = bTime;
    Assert.That(a.DateCreated, Is.EqualTo(bTime));
    Assert.That(b.DateCreated, Is.EqualTo(bTime));
}

This answers the question I thought was asked, not the one that was actually asked

I was able to get this to work using As<>(), but with a twist. Here's my test:

[Test]
public void Constructor_Always_Succeeds()
{
    var mockOfB = new Mock<B>();
    var mockOfA = mockOfB.As<A>();
    DateTime dateTime = DateTime.Now;

    mockOfA.SetupGet(p => p.DateCreated).Returns(dateTime);
    mockOfB.SetupGet(p => p.DateCreated).Returns(dateTime);

    B b = mockOfB.Object;
    A a = b;
    Assert.That(b.DateCreated, Is.EqualTo(dateTime));
    Assert.That(a.DateCreated, Is.EqualTo(dateTime));
}

That test passes, and without knowing more about your actual situation should work for you.


However, while initially testing this I had the SetupGet calls reversed (and was thinking you wanted different return values), like this:

[Test]
public void Constructor_Always_Succeeds()
{
    var mockOfB = new Mock<B>();
    var mockOfA = mockOfB.As<A>();
    DateTime aTime = DateTime.Now;
    DateTime bTime = DateTime.Now.AddDays(-1);

    // only difference, these two lines are swapped
    mockOfB.SetupGet(p => p.DateCreated).Returns(bTime);
    mockOfA.SetupGet(p => p.DateCreated).Returns(aTime);

    B b = mockOfB.Object;
    A a = b;
    Assert.That(b.DateCreated, Is.EqualTo(bTime));
    Assert.That(a.DateCreated, Is.EqualTo(aTime));
}

And in that case it fails at the first assertion because both calls to DateCreated return aTime. This is because of how Moq works internally, but I'd hesitate to call it a bug and there is a bug filed for it. When the call to the property is being intercepted, the setup that matches it is found with the following code:

localctx.Call = FluentMockContext.IsActive ? 
                (IProxyCall)null : 
                ctx.OrderedCalls.LastOrDefault(c => c.Matches(invocation));

In other words, "find the last setup that could be used as a match for this invocation." Since we setup A.DateCreated last, it could be used as a call for either B or A's DateCreated property.

Perhaps inside of MethodCall.Matches this situation should be handled.

like image 62
Patrick Quirk Avatar answered Oct 11 '22 11:10

Patrick Quirk