2

I've asked a question before on how to set value of interface in testing a method . I've successfully implemented the Moq framework into my project and the test runs fine.

this is the sample code that I've featured:

public void PostEvent(
            eVtCompId inSenderComponentId, 
            eVtEvtId inEventId, 
            long inEventReference, 
            IF_SerializableData inEventData)
{
    if(mEventMap.ContainsKey(inEventId))
    {
        mEventMap[inEventId](inSenderComponentId, inEventReference, inEventData);
    }
}

Here I have 4 parameters: 1st: an enum, 2nd: another enum, 3rd: long, 4th: an interface. However, I was mistaken in that the 4th parameter (the interface), isn't supposed to be an interface, but rather a reference to the interface.

so it should look like this:

public void PostEvent(
       eVtCompId inSenderComponentId, 
       eVtEvtId inEventId, 
       long inEventReference, 
       ref IF_SerializableData inEventData)

the sample Moq test code that was given to me (which is this)...

var serializable = new Mock<IF_SerializableData>();
target.PostEvent(..., serializable.Object);

...doesn't work. I've tried ref serializable.Object but it still doesn't work because I get an error that says the ref parameter is expecting a reference to a variable, not an object.

Any tips or examples on how to properly test this?

Community
  • 1
  • 1
Anthony
  • 437
  • 2
  • 8
  • 18
  • Consider using proper naming conventions. The names of your enumes are close to useless. `in` prefix is unneeded as out variables have a special designator. – Daniel Hilgarth Dec 11 '12 at 08:32
  • why would they be close to useless? I don't understand, why would you suggest something that *I think* wouldn't even help me in my situation? Naming conventions are the least of my concerns; I named them as such because it's a requirement. – Anthony Dec 11 '12 at 08:38
  • A name should convey a meaning. That's not the case with your names. My comment was meant to improve your overall coding. I am aware that this doesn't answer your question. That's actually the reason why I posted it as a comment and not as an answer. – Daniel Hilgarth Dec 11 '12 at 08:43
  • Oh okay. Yes, I know the deal about proper naming conventions, I've been practicing ever since my college years. But let's just say I named my variable types and variables names that way *because my boss says so*. Those naming conventions that he wants have meaning and actually make sense to the project that I'm currently assigned to. Savvy? :) – Anthony Dec 11 '12 at 08:49
  • Alright :-) (I wouldn't accept such naming conventions, no matter what the rational behind them) – Daniel Hilgarth Dec 11 '12 at 08:54

1 Answers1

3

You need to copy the Object reference from the serializable mock into a local variable so you can then pass it as ref.

IF_SerializableData localRef = serializable.Object;
target.PostEvent(..., ref localRef);

You can't pass ref Serializable.Object because its a property - see also Is it possible to pass properties as "out" or "ref" parameters? which offers an excellent discussion of why this is the case, and a source of other links.

My explanation

This is ultimately because properties are not variables. A read/write property is a pair get and set accessor method(s) providing variable-like capabilities but, crucially, when you get a property you always get a copy of the underlying variable - even if that variable has a reference type.

So:

public class MyClass {
  private object _property;
  public object Property {
    get { return _property; } //<-- _property reference copied on to stack & returned
                              //    here as a new variable.  Therefore a ref
                              //    on that is effectively meaningless.
                              //    for a ref to be possible you'd need a pointer 
                              //    to the _property variable (in C++ terms)
    set { _property = value; }
  }
}

In this example - if you could pass ref MyClass.Property to a method - it would be meaningless because it would be passing a reference to a transient variable on the stack - i.e. the copy of the reference returned by the Property get accessor; it would not be passing the property by reference. So C# doesn't allow it, even though it could, because it would imply thatProperty` can be modified by the method - when it simply can't.

Hence why we need to capture that value from the stack and copy it into a local variable in your case. Now - note that in order for the new value set by the ref method to appear on your Mock<T> you'd need to set it's Object property to the local variable value again (if you can - I don't use Moq but I assume it's Mocks are immutable).

A lot of debate has been had as to whether C# should automatically handle ref Property in this way (see the aforementioned SO I linked to). To my mind it's similar to ref Derived not being compatible with ref Base - yes there is a way that the language can handle this automatically for you, but should it? In my mind, no. Do I get frustrated by it? Oh yes of course - but often I find it highlights architectural weaknesses which really should be fixed (for example, relying on ref or out parameters where a return value is likely better).

The only way for C# to allow you to pass a property by reference would be to pass the get and set accessors to the target method - and this would not be compatible with ref at all (because that's just a memory location).

As a taster you'd have to write such a method something like this:

 public static void MyUglyRefMethod(Func<object> refGet, Action<object> refSet)
 {
   var read = refGet();
   var newValue = new object();
   refSet(newValue);
 }

With this, we can now provide ref like semantics over MyClass:

 MyClass a = new MyClass();
 MyUglyRefMethod(() => a.Property, (newValue) => a.Property = newValue);
 Assert.IsNotNull(a.Property);

But that is simply ugly as hell.

It's simpler to make a method to take a ref MyClass - and then it can write to any of the properties directly.

Community
  • 1
  • 1
Andras Zoltan
  • 41,961
  • 13
  • 104
  • 160
  • OMG it works XDDD can you explain to me as to why this is necessary? and what is actually happening behind that code? thanks a million :) – Anthony Dec 11 '12 at 08:58
  • 1
    I've put an explanation - not sure if it's better than anyone else's but I think it gets to the heart of the matter – Andras Zoltan Dec 11 '12 at 10:18