本文介绍了当元组<对象、bool&>代替提供的值时,Moq ReturnsAsync返回Null的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我正在使用Moq在单元测试中模拟POST API调用的服务层,并使用_service.Create(...)返回一个元组值:Task<(Model.Receipt Receipt, bool IsIdempotent)>
为此,我创建了一个元组结果并作为ReturnsAsync传递,如下所示:

var input = JsonConvert.DeserializeObject<Model.Receipt>(_jsonReceiptString);
var output = (Receipt: input, IsIdempotent: true);
_service.Setup(x => x.CreateAsync(input)).ReturnsAsync(output);
直到现在一切正常,但在运行时,在Post调用中调用服务后,返回值是<null,false>!!
这听起来像是返回一个缺省值,而不是预期的元组。因为我在这个模拟之后有日志记录数据,所以这会导致单元测试失败。
你知道我是不是遗漏了什么吗?

推荐答案

此设置

_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属性与inputIdName

匹配,则返回true
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<Receipt>,则问题中发布的原始代码将工作。这意味着该类有自己的内置逻辑,用于比较属性以查看两个实例是否相等。我使用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);
    }
}

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

这篇关于当元组&lt;对象、bool&>代替提供的值时,Moq ReturnsAsync返回Null的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-21 06:51