【问题标题】:Using Moq can you verify a method call with an anonymous type?使用 Moq 可以验证匿名类型的方法调用吗?
【发布时间】:2021-07-26 19:56:48
【问题描述】:

我正在尝试使用 Moq 验证方法调用,但我无法完全正确地获取语法。目前,我已将此作为我的验证:

repository.Verify(x => x.ExecuteNonQuery("fav_AddFavorites", new
{
    fid = 123,
    inputStr = "000456"
}), Times.Once());

代码可以编译,但测试失败并出现错误:

Expected invocation on the mock once, but was 0 times: 
x => x.ExecuteNonQuery("fav_AddFavorites", new <>f__AnonymousType0<Int32, String>(123, "000456"))
No setups configured.

Performed invocations:
IRepository.ExecuteNonQuery("fav_AddFavorites", { fid = 123, inputStr = 000456 })

如何验证方法调用并匹配匿名类型的方法参数?

更新

回答问题:

我正在尝试验证方法是否被调用以及参数是否正确。

我要验证的方法的签名是:

int ExecuteNonQuery(string query, object param = null);

设置代码很简单:

repository = new Mock<IRepository>();

更新 2

看起来这是 Moq 以及它如何处理 .Net 中的匿名类型的问题。 Paul Matovich 发布的代码运行良好,但是,一旦代码和测试位于不同的程序集中,测试就会失败。

【问题讨论】:

  • 你也可以发布设置代码吗?
  • 您能否发布您尝试验证的方法调用的签名,以及您是否尝试验证传递给该方法的参数,或者仅在它被调用时验证?
  • Rob 我测试了 Pauls 更新的答案,它适用于不同的程序集。也许你应该接受他的回答。

标签: moq


【解决方案1】:

通过了

        public class Class1
    {
        private Class2 _Class2;
        public Class1(Class2 class2)
        {
            _Class2 = class2;
        }

        public void DoSomething(string s)
        {
            _Class2.ExecuteNonQuery(s, new { fid = 123,  inputStr = "000456" });
        }
    }

    public class Class2
    {
        public virtual void ExecuteNonQuery(string s, object o)
        {
        }
    }

    /// <summary>
    ///A test for ExecuteNonQuery
    ///</summary>
    [TestMethod()]
    public void ExecuteNonQueryTest()
    {
        string testString = "Hello";
        var Class2Stub = new Mock<Class2>();
        Class1 target = new Class1(Class2Stub.Object);
        target.DoSomething(testString);
        Class2Stub.Verify(x => x.ExecuteNonQuery(testString, It.Is<object>(o => o.Equals(new { fid = 123, inputStr = "000456" }))), Times.Once());
    }

##更新##

这很奇怪,它在不同的程序集中不起作用。有人可以给我们详细解释为什么不同程序集的 object.equals 行为不同,但是对于不同的程序集,这将起作用,对象值的任何差异都会返回不同的哈希码。

        Class2Stub.Verify(x => x.ExecuteNonQuery(testString, It.Is<object>(o => o.GetHashCode() == (new { fid = 123, inputStr = "000456" }).GetHashCode())), Times.Once());

【讨论】:

  • Object equals 的工作方式不同,因为如果它来自不同的程序集,则对象的类型是不同的,这意味着不同的命名空间。另请注意,匿名类型会覆盖 Equals 和 GetHashCode 方法。有关详细信息,请参阅此link
  • 感谢 Rok 提供的额外信息和我的回答。 :-)
  • 来自MS Docs - 如果程序集中的两个或多个匿名对象初始值设定项指定顺序相同且名称和类型相同的属性序列,编译器会将对象视为实例同类型。它们共享相同的编译器生成的类型信息。
【解决方案2】:

一种选择是在回调中“验证”它。显然这需要在设置时完成,例如:

aMock.Setup(x => x.Method(It.IsAny<object>())).Callback<object>(
  (p1) =>
    {
      dynamic o = p1;
      Assert.That(o.Name, Is.EqualTo("Bilbo"));
    });

【讨论】:

  • 我的单元测试在不同的程序集中。为了完成这项工作,我在正在测试的项目中的 Assembly.cs 中添加了以下内容 - [assembly: InternalsVisibleTo("UnitTest")]。在此更改之前,我收到以下错误:Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:'object' does not contain a definition for 'Name'
【解决方案3】:

当您的测试程序集与被测系统的程序集不同(非常常见)时,没有一个答案很好。这是我使用 JSON 序列化然后字符串比较的解决方案。

测试助手功能:

using Newtonsoft.Json;

public static class VerifyHelper
{
    public static bool AreEqualObjects(object expected, object actual)
    {
        var expectedJson = JsonConvert.SerializeObject(expected);
        var actualJson = JsonConvert.SerializeObject(actual);

        return expectedJson == actualJson;
    }
}

被测系统示例:

public void DoWork(string input)
{
     var obj = new { Prop1 = input };
     dependency.SomeDependencyFunction(obj);
}

示例单元测试:

var expectedObject = new { Prop1 = "foo" };
    
sut.DoWork("foo");
dependency.Verify(x => x.SomeDependencyFunction(It.Is<object>(y => VerifyHelper.AreEqualObjects(expectedObject, y))), Times.Once());

这个解决方案非常简单,我认为与该线程中的其他答案相比,单元测试更容易理解。但是,因为它使用简单的字符串比较,所以测试的匿名对象必须设置为与系统下测试的匿名对象完全相同。因此,假设您只关心验证单个属性的值,但是您的测试系统在匿名对象上设置了其他属性,您的单元测试将需要为帮助函数返回 true。

【讨论】:

  • 使用 json 序列化程序可能不是每个人都喜欢的,但您在这里了解简单性和可读性。这种技术非常有效。谢谢!
【解决方案4】:

我根据 Pauls 的回答创建了一个可重用的方法:

object ItIsAnonymousObject(object value)
{
    return It.Is<object>(o => o.GetHashCode() == value.GetHashCode());
}

...

dependency.Verify(
    x => x.SomeDependencyFunction(ItIsAnonymousObject(new { Prop1 = "foo" })),
    Times.Once());

此外,这可用于属性名称不区分大小写的比较:

protected object ItIsAnonymousObject(object value)
{
    var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
    return It.Is<object>(o => JsonSerializer.Serialize(o, options) == JsonSerializer.Serialize(value, options));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-09
    • 1970-01-01
    • 2010-11-15
    相关资源
    最近更新 更多