tags:

views:

1604

answers:

3

I just switched to Moq and have run into a problem. I'm testing a method that creates a new instance of a business object, sets the properties of the object from user input values and calls a method (SaveCustomerContact ) to save the new object. The business object is passed as a ref argument because it goes through a remoting layer. I need to test that the object being passed to SaveCustomerContact has all of its properties set as expected, but because it is instantiated as new in the controller method I can't seem to do so.

public void AddContact() {

    var contact = new CustomerContact() { CustomerId = m_model.CustomerId };

    contact.Name = m_model.CustomerContactName;
    contact.PhoneNumber = m_model.PhoneNumber;
    contact.FaxNumber = m_model.FaxNumber;
    contact.Email = m_model.Email;
    contact.ReceiveInvoiceFlag = m_model.ReceiveInvoiceFlag;
    contact.ReceiveStatementFlag = m_model.ReceiveStatementFlag;
    contact.ReceiveContractFlag = m_model.ReceiveContractFlag;
    contact.EmailFlag = m_model.EmailFlag;
    contact.FaxFlag = m_model.FaxFlag;
    contact.PostalMailFlag = m_model.PostalMailFlag;
    contact.CustomerLocationId = m_model.CustomerLocationId;

    RemotingHandler.SaveCustomerContact( ref contact );
}

Here's the test:

[TestMethod()]
public void AddContactTest() {

    int customerId = 0;

    string name = "a";

    var actual = new CustomerContact();

    var expected = new CustomerContact() {
        CustomerId = customerId,
        Name = name
    };

    model.Setup( m => m.CustomerId ).Returns( customerId );
    model.SetupProperty( m => model.CustomerContactName, name );
    model.SetupProperty( m => m.PhoneNumber, string.Empty );
    model.SetupProperty( m => m.FaxNumber, string.Empty );
    model.SetupProperty( m => m.Email, string.Empty );
    model.SetupProperty( m => m.ReceiveInvoiceFlag, false );
    model.SetupProperty( m => m.ReceiveStatementFlag, false );
    model.SetupProperty( m => m.ReceiveContractFlag, false );
    model.SetupProperty( m => m.EmailFlag, false );
    model.SetupProperty( m => m.FaxFlag, false );
    model.SetupProperty( m => m.PostalMailFlag, false );
    model.SetupProperty( m => m.CustomerLocationId, 0 );

    remote
        .Setup( r => r.SaveCustomerContact( ref actual ) )
        .Callback( () => Assert.AreEqual( actual, expected ) );

    target.AddContact();

}

This is just the most recent of many attempts to get ahold of that parameter. For reference, the value of actual does not change from its initial (constructed) state.

Moving the Assert.AreEqual(expected, actual) after the target call fails. If I add .Verifiable() to the setup instead of the .CallBack and then call remote.Verify after the target (or, I assume, set the mock to strict) it always fails because the parameter I provide in the test is not the same instance as the one that is created in the controller method.

I'm using Moq 3.0.308.2. Any ideas on how to test this would be appreciated. Thanks!

+1  A: 

Unfortunately, I am not sure that this is possible without direct support from Moq. The problem is that Lambda expressions do not support ref or out.

"A lambda expression cannot directly capture a ref or out parameter from an enclosing method. "

http://msdn.microsoft.com/en-us/library/bb397687.aspx

I can't even get an example like yours to work. Adding ref to the setup fails to compile.

You might want to check out the Moq discussions for more http://groups.google.com/group/moqdisc

Good luck.

Brian Genisio
+3  A: 

I can't offer you an exact solution, but an alternative would be to hide the pass-by-ref semantics behind an adapter, which takes the parameter by value and forwards it to the RemotingHandler. This would be easier to mock, and would remove the "ref" wart from the interface (I am always suspicious of ref parameters :-) )

EDIT:

Or you could use a stub instead of a mock, for example:

public class StubRemotingHandler : IRemotingHandler
{
    public CustomerContact savedContact;

    public void SaveCustomerContact(ref CustomerContact contact)
    {
        savedContact = contact;
    }
}

You can now examine the saved object in your test:

IRemotingHandler remote = new StubRemotingHandler();
...
//pass the stub to your object-under-test
...
target.AddContact();
Assert.AreEqual(expected, remote.savedContact);

You also say in your comment:

I'd hate to start a precedent of wrapping random bits of the backend so I can write tests more easily

I think that's exactly the precedent you need to set! If your code isn't testable, you're going to keep struggling to test it. Make it easier to test, and increase your coverage.

Jim Arnold
I guess I'm not sure how you'd stub this either (though I don't know much about stubs). Can you elaborate on how that might be handled?
boyhowdy
Also, as much as I agree with you on the ref issue, that's the way our remoting layer works and I'd hate to start a precedent of wrapping random bits of the backend so I can write tests more easily.
boyhowdy
I was having a similar issue, and got so zeroed in on making MoQ work, I forgot I could write my own stub! Thank you.
Eddie Deyo
A: 

The latest version of Moq supports this scenario.

Taken from the quickstart at http://code.google.com/p/moq/wiki/QuickStart:

// ref arguments
var instance = new Bar();
// Only matches if the ref argument to the invocation is the same instance
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);
Praveen Angyan
Thanks for the response. I have seen this in the quickstart (in fact, I convinced my boss to upgrade to the latest version of Moq because I was banking on it working). Unfortunately, the behavior does not seem to follow what is (very unclearly) suggested by "Only matches if the ref argument to the invocation is the same instance".
boyhowdy