【问题标题】:Moq ReturnsAsync returns Null when tuple<object,bool> instead of provided valueMoq ReturnsAsync 在 tuple<object,bool> 而不是提供的值时返回 Null
【发布时间】:2022-01-27 06:00:00
【问题描述】:

这是我到目前为止的第一个问题,如果描述得不是很好,请原谅,但我会尽力而为。

我正在使用 Moq 在单元测试中模拟我的服务层以进行后 api 调用,并且 _service.Create(...) 返回一个元组值: Task&lt;(Model.Receipt Receipt, bool IsIdempotent)&gt;
为此,我创建了一个元组结果并作为 ReturnsAsync 传递,如下所示:

var input = JsonConvert.DeserializeObject<Model.Receipt>(_jsonReceiptString);
var output = (Receipt: input, IsIdempotent: true);
_service.Setup(x => x.CreateAsync(input)).ReturnsAsync(output);

到这里一切正常,但在运行时,在 Post 调用中调用服务后,返回值为&lt;null,false&gt;!!!
这听起来像是返回一个默认值而不是预期的元组。由于我在此模拟之后记录了数据,这会导致单元测试失败。
如果我在这里遗漏了什么,你知道吗?

【问题讨论】:

  • 你能把你的测试正在测试的代码贴出来吗?
  • 我很确定Model.Receipt 是一个类并且没有Equals 重载。
  • 作为一个实验,将_service.Setup(x =&gt; x.CreateAsync(input)).ReturnsAsync(output); 更改为_service.Setup(x =&gt; x.CreateAsync(It.IsAny&lt;Model.Receipt&gt;)).ReturnsAsync(output); 如果它有效,则说明在原始代码中,您的设置期望使用Model.Receipt 的相同实例调用该方法.您可以更改您的测试以确保将相同的实例传递给该方法,或者将您的Setup 更改为使用It.Is&lt;Model.Receipt&gt;(...function that checks to see if the argument matches some criteria...);
  • @ScottHannen 哦,成功了!谢谢...您是说 _service.CreateAsync 期望输入为 API?因为我为此使用自动映射器?是这个原因吗?这是实际的 GET 方法 var data = await _service.CreateAsync(_mapper.Map(request));
  • 这是因为在原始版本中它期望相同的类实例。现在您可以使用It.Is 检查传递给函数的Receipt。如果您更新问题以显示您想要检查的属性或Receipt 中的一些,那么我可以将其余部分放在答案中。换句话说,Receipt 需要具有哪些值才能让您知道它是正确的并且测试应该通过?

标签: c# asp.net-web-api nunit moq .net-6.0


【解决方案1】:

这个设置

_service.Setup(x => x.CreateAsync(input)).ReturnsAsync(output);

..说如果CreateAsync被调用并且参数是input,返回output。这仅适用于参数实际上是同一个对象、与input 相同的类实例的情况。

有时有效,但通常无效。在这种情况下,input 是从字符串反序列化的。如果您正在测试的代码还反序列化一个字符串,您最终会得到两个Receipt 实例。它们可能是相同的,但它们不是同一个实例,因此 Setup 不能按您想要的方式工作。

您可能想要设置模拟,以便如果您调用 CreateAsyncinput 具有某些属性值,那么模拟返回 output

我不知道Receipt 长什么样。为了演示,假设它看起来像这样

internal class Receipt
{
    public int Id { get; set; }
    public string Name { get; set; }
}

...如果传递给CreateAsync 的参数与input 具有相同的IdName,您希望Setup 返回output。在这种情况下,您可以这样做:

_service.Setup(x =>
        x.CreateAsync(
            It.Is<Models.Receipt>(receipt =>
                receipt.Id == input.Id
                && receipt.Name == input.Name)))
    .ReturnsAsync(output);

这表示Setup 正在寻找Receipt 参数,当它获取该参数时,它将执行此函数,如果参数的IdName 属性匹配input,则返回trueIdName

receipt =>
    receipt.Id == input.Id
    && receipt.Name == input.Name

如果您打算编写大量此类测试并且不想一遍又一遍地编写该函数怎么办?您还可以像这样创建IEqualityComparer

public class ReceiptEqualityComparer : IEqualityComparer<Receipt>
{
    public bool Equals(Receipt x, Receipt y)
    {
        return x.Id == y.Id && x.Name == y.Name;
    }

    public int GetHashCode(Receipt obj)
    {
        return HashCode.Combine(obj.Id, obj.Name);
    }
}

除非您在生产代码中需要这个,否则我会在测试项目中定义这个类。此类包含比较Receipt 的两个实例并确定它们是否相等的逻辑。

现在您的Setup 可以如下所示:

_service.Setup(x =>
        x.CreateAsync(
            It.Is<Models.Receipt>(input, new ReceiptEqualityComparer())))
    .ReturnsAsync(output);

现在Setup 将采用传递给CreateAsync 的参数并使用ReceiptEqualityComparer 来确定该参数是否“等于”input。如果它们具有相同的IdName,它们是相等的。


最后,如果Receipt 实现IEquatable&lt;Receipt&gt;,您在问题中发布的原始代码将起作用。这意味着该类具有自己的内置逻辑,用于比较属性以查看两个实例是否相等。我使用 ReSharper 为我自动生成它。它看起来像这样:

public class Receipt : IEquatable<Receipt>
{
    public int Id { get; set; }
    public string Name { get; set; }

    public bool Equals(Receipt? other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Id == other.Id && Name == other.Name;
    }

    public override bool Equals(object? obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Receipt) obj);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Id, Name);
    }
}

这可能是有道理的。但是,如果您只需要它来进行测试,那么我更喜欢前两种方法中的任何一种,而不是修改生产类以便测试工作。

【讨论】:

    猜你喜欢
    • 2021-11-10
    • 2017-04-11
    • 1970-01-01
    • 2015-05-29
    • 2016-03-05
    • 2018-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多