使用Moq验证引用参数的值

18

我刚刚转换到Moq并遇到了一个问题。我正在测试一个方法,该方法创建一个新的业务对象实例,从用户输入值设置对象属性,并调用一个方法(SaveCustomerContact)来保存新对象。因为业务对象通过远程处理层传递,所以它作为ref参数传递。我需要测试传递给SaveCustomerContact的对象是否像预期设置了所有属性,但是因为它在控制器方法中被实例化为新对象,我似乎做不到。

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

这里是测试:

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

}

这只是尝试获取该参数的最新尝试之一。 值得参考的是,actual的值不会从其初始(构造)状态更改。

将Assert.AreEqual(expected, actual)移动到目标调用后会失败。 如果我在设置中添加.Verifiable()而不是.CallBack并在目标之后调用remote.Verify(或者,我假设,将模拟设置为严格模式),则始终会失败,因为我在测试中提供的参数与在控制器方法中创建的实例不同。

我正在使用Moq 3.0.308.2。 任何有关如何测试这个问题的想法都将不胜感激。 谢谢!

4个回答

19

我不能给你一个确切的解决方案,但是一个替代方案是,在适配器后面隐藏传递引用语义,适配器接受值参数并将其转发到RemotingHandler。这样做会更容易进行模拟,并从接口中删除"ref"疣(我总是对ref参数持怀疑态度:-))

编辑:

或者您可以使用存根而不是模拟,例如:

public class StubRemotingHandler : IRemotingHandler
{
    public CustomerContact savedContact;

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

现在,您可以检查测试中保存的对象:

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

你在评论中还说:

我不想开个头,将后端的随机部分包装起来,这样我就可以更容易地编写测试。

我认为这正是你需要确立的先例!如果你的代码无法进行测试,那么你将继续苦于测试它。让测试变得更容易,并增加覆盖率。


我猜我也不确定你如何存根这个(虽然我对存根不太了解)。你能详细说明一下如何处理吗? - Simon Calvin
同样地,尽管我在引用问题上同意您的观点,但这就是我们远程层的工作方式,我不想为了更轻松地编写测试而开始包装后端的随机位,从而开创先例。 - Simon Calvin
我曾经遇到类似的问题,一心想让MoQ能够工作,忘记了我可以自己编写存根!谢谢。 - Eddie Deyo

10
最新版本的Moq支持这种情况。取自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);

6
谢谢回复。我在快速入门指南中看到过这个(事实上,我说服了我的老板升级到最新版本的Moq,因为我想它可以正常工作)。不幸的是,这个行为似乎与“仅当对调用的ref参数是同一实例时才匹配”所建议的内容(非常不清晰)不符。 - Simon Calvin

9

0

我遇到了类似的问题。但是我通过使用最新的Moq并像下面这样传递值来解决了它:

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

之前,我也在使用同样的方法,但是我没有得到true作为返回值。

在实际函数内部,instance被创建并覆盖了从单元测试类传递过来的instance,导致了问题。我删除了实际类内部的instance创建,然后它就可以工作了。

希望这能帮到你。

谢谢


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接