简体   繁体   English

如何使用 moq 来验证是否将类似的对象作为参数传入?

[英]How to use moq to verify that a similar object was passed in as argument?

I have had a few occasions where something like this would be helpful.我有几次这样的事情会有所帮助。 I have, for instance, an AccountCreator with a Create method that takes a NewAccount .例如,我有一个带有Create方法的AccountCreator ,该方法采用NewAccount My AccountCreator has an IRepository that will eventually be used to create the account.我的AccountCreator有一个IRepository ,最终将用于创建帐户。 My AccountCreator will first map the properties from NewAccount to Account , second pass the Account to the repo to finally create it.我的AccountCreator将首先将属性从NewAccount映射到Account ,然后将Account传递给 repo 以最终创建它。 My tests look something like this:我的测试看起来像这样:

public class when_creating_an_account
{
    static Mock<IRepository> _mockedRepository;
    static AccountCreator _accountCreator;
    static NewAccount _newAccount;
    static Account _result;
    static Account _account;

    Establish context = () =>
        {
            _mockedRepository = new Mock<IRepository>();
            _accountCreator = new AccountCreator(_mockedRepository.Object);

            _newAccount = new NewAccount();
            _account = new Account();

            _mockedRepository
                .Setup(x => x.Create(Moq.It.IsAny<Account>()))
                .Returns(_account);
        };

    Because of = () => _result = _accountCreator.Create(_newAccount);

    It should_create_the_account_in_the_repository = () => _result.ShouldEqual(_account);
}

So, what I need is something to replace It.IsAny<Account> , because that doesn't help me verify that the correct Account was created.所以,我需要的是替换It.IsAny<Account>的东西,因为这并不能帮助我验证是否创建了正确的帐户。 What would be amazing is something like...令人惊奇的是……

public class when_creating_an_account
{
    static Mock<IRepository> _mockedRepository;
    static AccountCreator _accountCreator;
    static NewAccount _newAccount;
    static Account _result;
    static Account _account;

    Establish context = () =>
        {
            _mockedRepository = new Mock<IRepository>();
            _accountCreator = new AccountCreator(_mockedRepository.Object);

            _newAccount = new NewAccount
                {
                    //full of populated properties
                };
            _account = new Account
                {
                    //matching properties to verify correct mapping
                };

            _mockedRepository
                .Setup(x => x.Create(Moq.It.IsLike<Account>(_account)))
                .Returns(_account);
        };

    Because of = () => _result = _accountCreator.Create(_newAccount);

    It should_create_the_account_in_the_repository = () => _result.ShouldEqual(_account);
}

Notice I changed It.IsAny<> to It.IsLike<> and passed in a populated Account object.请注意,我将It.IsAny<>更改为It.IsLike<>并传入一个填充的Account对象。 Ideally, in the background, something would compare the property values and let it pass if they all match.理想情况下,在后台,一些东西会比较属性值,如果它们都匹配则让它通过。

So, does it exist already?那么,它已经存在了吗? Or might this be something you have done before and wouldn't mind sharing the code?或者这可能是您以前做过的事情并且不介意共享代码?

To stub out a repository to return a particular value based on like criteria, the following should work:要存根存储库以根据类似条件返回特定值,以下应该有效:

_repositoryStub
    .Setup(x => x.Create(
        Moq.It.Is<Account>(a => _maskAccount.ToExpectedObject().Equals(a))))
    .Returns(_account);

The following should work for you:以下内容应该适合您:

Moq.It.Is<Account>(a=>a.Property1 == _account.Property1)

However as it was mentioned you have to implement matching criteria.但是,正如前面提到的,您必须实施匹配标准。

I have done that using the FluentAssertians library (which is much more flexible and has lot's of goodies), as in the following example:我已经使用FluentAssertians 库(它更灵活并且有很多好东西)做到了这一点,如下例所示:

_mockedRepository
        .Setup(x => x.Create(Moq.It.Is<Account>(actual =>
                   actual.Should().BeEquivalentTo(_account, 
                       "") != null)))
        .Returns(_account);

Note the empty argument, which is needed since this is a lambda expression which can't use default parameters.注意空参数,这是必需的,因为这是一个不能使用默认参数的 lambda 表达式。

Also note the != null expression which is just to convert it to bool so to be able to compile, and to be able to pass when it is equal, since when it is not equal then FluentAssertians will throw.还要注意!= null表达式,它只是将其转换为bool以便能够编译,并且能够在它相等时通过,因为当它不相等时, FluentAssertians将抛出。


Note that this only works in newer versions of FluentAssertians , for older versions you can do a similar method described in http://www.craigwardman.com/Blogging/BlogEntry/using-fluent-assertions-inside-of-a-moq-verify请注意,这仅适用于FluentAssertians的较新版本,对于旧版本,您可以执行http://www.craigwardman.com/Blogging/BlogEntry/using-fluent-assertions-inside-of-a-moq-中描述的类似方法核实

It involves using an AssertionScope as in the following code它涉及使用AssertionScope ,如下面的代码所示

public static class FluentVerifier
{
    public static bool VerifyFluentAssertion(Action assertion)
    {
        using (var assertionScope = new AssertionScope())
        {
             assertion();

             return !assertionScope.Discard().Any();
        }
    }
 }

And then you can do:然后你可以这样做:

_mockedRepository
            .Setup(x => x.Create(Moq.It.Is<Account>(actual => 
                   FluentVerifier.VerifyFluentAssertion(() =>
                       actual.Should().BeEquivalentTo(_account, "")))))))
            .Returns(_account);

I was not able to find anything that does exactly what is described in the question.我找不到任何与问题中描述的完全一致的东西。 In the mean time, the best way I can find to handle verification of objects passed in as arguments to mocked methods (without the luxury of referencial equality) is a combination of Callback and the Expected Object pattern to compare the actual with the expected object:同时,我能找到的处理作为参数传递给模拟方法的对象的验证的最佳方法(没有引用相等的奢侈)是Callback和预期对象模式的组合,以将实际对象与预期对象进行比较:

public class when_creating_an_account
{
    static Mock<IRepository> _mockedRepository;
    static AccountCreator _accountCreator;
    static NewAccount _newAccount;
    static Account _result;
    static Account _expectedAccount;
    static Account _actualAccount;

    Establish context = () =>
        {
            _mockedRepository = new Mock<IRepository>();
            _accountCreator = new AccountCreator(_mockedRepository.Object);

            _newAccount = new NewAccount
                {
                    //full of populated properties
                };
            _expectedAccount = new Account
                {
                    //matching properties to verify correct mapping
                };

            _mockedRepository
                .Setup(x => x.Create(Moq.It.IsAny<Account>(_account)))
                //here, we capture the actual account passed in.
                .Callback<Account>(x=> _actualAccount = x) 
                .Returns(_account);
        };

    Because of = () => _result = _accountCreator.Create(_newAccount);

    It should_create_the_account_in_the_repository = 
        () => _result.ShouldEqual(_account);

    It should_create_the_expected_account = 
        () => _expectedAccount.ToExpectedObject().ShouldEqual(_actualAccount);
}

The expected object pattern is great, but it's complicated to implement in C#, so I use a library that handles all that for me.预期的对象模式很棒,但是在 C# 中实现起来很复杂,所以我使用了一个库来为我处理所有这些。 https://github.com/derekgreer/expectedObjects https://github.com/derekgreer/expectedObjects

My last observation looks at the properties in the actual account passed in and compares each to the same property on my "expected object".我最后一次观察查看传入的实际帐户中的属性,并将每个属性与我的“预期对象”上的相同属性进行比较。 This way I don't have a huge list of mock property checks, nor do I have a ton of test observations.这样我就没有大量的模拟属性检查列表,也没有大量的测试观察结果。

If I use JSONConvert.SerializeObject to covert the expected and actual objects to JSON strings, and then do an equals between them, it does seem to produce an acceptable result.如果我使用JSONConvert.SerializeObject将预期对象和实际对象转换为 JSON 字符串,然后在它们之间进行equals ,它似乎确实会产生可接受的结果。 My thinking is that if a string representation of the objects match, then their public properties are most likely the same too.我的想法是,如果对象的字符串表示匹配,那么它们的公共属性很可能也是相同的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM