Verify value of reference parameter with Moq

南楼画角 提交于 2019-11-30 00:44:54

问题


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:


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.




回答2:


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



回答3:


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.




回答4:


I have encountered similar issue. Bit I got the solution by using the latest Moq and passing the value like

var instance = new Bar(); Mock.Setup(foo => foo.Submit(ref instance)).Returns(true);

Earlier as well, I was using the same method, but i was not getting return as true.

Inside the actual function instance was getting created and overwriting the instance passed from the unit test class was causing the issue. I removed instance creation inside the actual class and then it worked.

Hope it will help you.

thanks



来源:https://stackoverflow.com/questions/726630/verify-value-of-reference-parameter-with-moq

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!