[英]Moq ReturnsAsync returns Null when tuple<object,bool> instead of provided value
This is my first question so far, excuse me if it is not very well-described but I will try my best.到目前为止,这是我的第一个问题,如果描述得不是很好,请原谅,但我会尽力而为。
I am using Moq to mock my service layer in unit test for a post api call, and the _service.Create(...) returns a tuple value: Task<(Model.Receipt Receipt, bool IsIdempotent)>
我正在使用 Moq 在 api 后调用的单元测试中模拟我的服务层,并且 _service.Create(...) 返回一个元组值: Task<(Model.Receipt Receipt, bool IsIdempotent)>
therefor I created a tuple result and pass as ReturnsAsync like below:为此,我创建了一个元组结果并作为 ReturnsAsync 传递,如下所示:
var input = JsonConvert.DeserializeObject<Model.Receipt>(_jsonReceiptString);
var output = (Receipt: input, IsIdempotent: true);
_service.Setup(x => x.CreateAsync(input)).ReturnsAsync(output);
everything works fine till here, but at run time, after calling the service in the Post call, the return value is <null,false>
!!!到目前为止一切正常,但在运行时,在 Post 调用中调用服务后,返回值为<null,false>
!!!
It is sound like returning a default value instead of expected tuple.这听起来像是返回默认值而不是预期的元组。 As I have logging data after this mocking, this causes a failed unit test.由于我在此 mocking 之后记录了数据,这会导致单元测试失败。
Do you have any idea if I am missing something here?如果我在这里遗漏了什么,你知道吗?
This setup这个设置
_service.Setup(x => x.CreateAsync(input)).ReturnsAsync(output);
..says that if CreateAsync
is called and the argument is input
, return output
. ..说如果CreateAsync
并且参数是input
,则返回output
。 This only works if the argument is actually the same object, the same class instance as input
.这仅在参数实际上是相同的 object、相同的 class 实例作为input
时才有效。
Sometimes that works, but often it doesn't.有时这有效,但通常无效。 In this case input
is deserialized from a string.在这种情况下, input
是从字符串反序列化的。 If the code you're testing also deserializes a string you'll end up with two instances of Receipt
.如果您正在测试的代码还反序列化一个字符串,您最终会得到两个Receipt
实例。 They might be identical, but they're not the same instance so the Setup
doesn't work the way you want.它们可能相同,但它们不是同一个实例,因此Setup
无法按您希望的方式工作。
What you likely want is to set up the mock so that if you call CreateAsync
and input
has certain property values then the mock returns output
.您可能想要设置模拟,以便如果您调用CreateAsync
并且input
具有某些属性值,则模拟返回output
。
I don't know what Receipt
looks like.我不知道Receipt
是什么样的。 For the sake of demonstration let's say it looks like this为了演示,假设它看起来像这样
internal class Receipt
{
public int Id { get; set; }
public string Name { get; set; }
}
...and you want your Setup
to return output
if the argument passed to CreateAsync
has the same Id
and Name
as input
. ...如果传递给CreateAsync
的参数与input
具有相同的Id
和Name
,您希望您的Setup
返回output
。 In that case you could do this:在这种情况下,您可以这样做:
_service.Setup(x =>
x.CreateAsync(
It.Is<Models.Receipt>(receipt =>
receipt.Id == input.Id
&& receipt.Name == input.Name)))
.ReturnsAsync(output);
This says that Setup
is looking for a Receipt
argument, and when it gets that argument it's going to execute this function which returns true
if the argument's Id
and Name
property match input
's Id
and Name
:这表示Setup
正在寻找一个Receipt
参数,当它获取该参数时,它将执行此 function 如果参数的Id
和Name
属性匹配input
的Id
和Name
,则返回true
:
receipt =>
receipt.Id == input.Id
&& receipt.Name == input.Name
What if you plan on writing a lot of these tests and you don't want to write that function over and over?如果您打算编写大量此类测试并且不想一遍又一遍地编写 function 怎么办? You can also create an IEqualityComparer
like this:您还可以像这样创建一个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);
}
}
Unless you need this in your production code I would define this class in the test project.除非您在生产代码中需要这个,否则我会在测试项目中定义这个 class。 This class contains the logic to compare two instances of Receipt
and decide if they are equal.此 class 包含比较两个Receipt
实例并确定它们是否相等的逻辑。
Now your Setup
can look like this:现在您的Setup
可能如下所示:
_service.Setup(x =>
x.CreateAsync(
It.Is<Models.Receipt>(input, new ReceiptEqualityComparer())))
.ReturnsAsync(output);
Now Setup
will take the argument passed to CreateAsync
and use ReceiptEqualityComparer
to determine whether that argument is "equal" to input
.现在, Setup
将获取传递给CreateAsync
的参数并使用ReceiptEqualityComparer
来确定该参数是否“等于” input
。 If they have the same Id
and Name
they are equal.如果它们具有相同的Id
和Name
,它们是相等的。
Finally, your original code as posted in the question will work if Receipt
implements IEquatable<Receipt>
.最后,如果Receipt
实现IEquatable<Receipt>
,则问题中发布的原始代码将起作用。 That means that the class has its own built-in logic for comparing properties to see if two instances are equal.这意味着 class 具有自己的内置逻辑,用于比较属性以查看两个实例是否相等。 I use ReSharper to auto-generate that for me.我使用 ReSharper 为我自动生成它。 It looks like this:它看起来像这样:
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);
}
}
That might make sense.这可能是有道理的。 But if you only need this for your test then I'd prefer either of the previous two approaches instead of modifying the production class so that the test works.但是,如果您只需要它来进行测试,那么我更喜欢前两种方法中的任何一种,而不是修改生产 class 以便测试有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.