Moq return 在第二次执行时设置 returning 错误数据
Moq return setup returning wrong data on second execution
我在单元测试中设置 return 值时遇到问题,导致在第二次执行时 return 出现意外数据。
我正在测试我创建的服务,我有以下代码从服务中使用的接口设置 return 数据。
_dataService.Setup(x => x.GetJsonFromApi(It.IsAny<string>(), API_KEY, ENTITY_A))
.Returns(Task.FromResult(new List<Entity>() {
new Entity {
Forename = "first_name",
Surname = "last_name",
EntityType = ENTITY_A
}
}));
_dataService.Setup(x => x.GetJsonFromApi(It.IsAny<string>(), API_KEY, ENTITY_B))
.Returns(Task.FromResult(new List<Entity>() {
new Entity {
Forename = "first_name",
Surname = "last_name",
EntityType = ENTITY_B
}
}));
_dataService.Setup(x => x.GetJsonFromApi(It.IsAny<string>(), API_KEY, ENTITY_C))
.Returns(Task.FromResult(new List<Entity>() {
new Entity {
Forename = "first_name",
Surname = "last_name",
EntityType = ENTITY_C
}
}));
在每次调用 returning 一个对象时,在单元测试中第一次执行我的服务时,上面的代码工作正常。服务中的代码是:
var data = await _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_A);
data.AddRange(await _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_B));
data.AddRange(await _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_C));
关于第二次执行;第一次调用(例如 _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_A))
是 returning 三个对象的列表而不是预期的对象。我允许调试器继续执行以下两个调用 data.AddRange() 和预期的单个对象是 return 从第二次执行的两个对象中编辑并添加到列表中,所以我最终得到五个对象。
有没有人能告诉我我做错了什么。
一些附加信息:
代码在单元测试方法中执行了两次为:
apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();
apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();
传递给GetJsonFromApi
方法的属性都是字符串,而ENTITY_A、ENTITY_B和ENTITY_C是常量,所以在第二次执行时传递给的所有参数功能应该完全一样。
我正在测试应在第二个 运行 上锁定的服务的另一部分,但需要将其正确 运行 以便测试进行。
很可能(在 99% 的情况下)那是因为您正在使用 .Returns()
的重载,它采用现成的值。请注意代码中 .Returns()
的参数是什么:这是一个任务。已经建成。从值构造。已经构建的价值。这是一个列表。
这意味着模拟将记住这个 List<> 对象并在以后重用它。任何时候任何人想要 GetJsonFromApi
给定参数,他们都会得到完全相同的对象实例。你的模拟不会给他们一个具有相似内容的新列表(就像一个正常的 HTTP/etc 客户端的行为),但总是 return 相同的对象实例。
现在,如果您的代码在别处获取该列表并向其追加新项目,会发生什么情况?你的模拟不会注意到。它仍然会很高兴 return 相同的列表实例。现在有更多项目。
我打赌这就是发生的事情。
出于这个原因,.Returns
方法也有一个接受委托的重载:
而不是:foo.Returns(new List<int>{ 1, 2, 3 })
尝试使用:foo.Returns(() => new List<int>{ 1, 2, 3 })
而不是:foo.Returns(Task.FromResult(new List<int>{ 1, 2, 3 }))
尝试使用:foo.Returns(() => Task.FromResult(new List<int>{ 1, 2, 3 }))
甚至:foo.ReturnsAsync(() => new List<int>{ 1, 2, 3 })
这样,模拟缓存的唯一内容就是 lambda,并且在调用模拟方法之前不会执行 lambda。然后,每次调用模拟方法时,都会再次执行 lambda,并且 returns 是一个新构造的对象。如果稍后有任何代码修改了该对象,也没关系,因为下一次调用模拟方法将构建另一个新响应。
我在单元测试中设置 return 值时遇到问题,导致在第二次执行时 return 出现意外数据。
我正在测试我创建的服务,我有以下代码从服务中使用的接口设置 return 数据。
_dataService.Setup(x => x.GetJsonFromApi(It.IsAny<string>(), API_KEY, ENTITY_A))
.Returns(Task.FromResult(new List<Entity>() {
new Entity {
Forename = "first_name",
Surname = "last_name",
EntityType = ENTITY_A
}
}));
_dataService.Setup(x => x.GetJsonFromApi(It.IsAny<string>(), API_KEY, ENTITY_B))
.Returns(Task.FromResult(new List<Entity>() {
new Entity {
Forename = "first_name",
Surname = "last_name",
EntityType = ENTITY_B
}
}));
_dataService.Setup(x => x.GetJsonFromApi(It.IsAny<string>(), API_KEY, ENTITY_C))
.Returns(Task.FromResult(new List<Entity>() {
new Entity {
Forename = "first_name",
Surname = "last_name",
EntityType = ENTITY_C
}
}));
在每次调用 returning 一个对象时,在单元测试中第一次执行我的服务时,上面的代码工作正常。服务中的代码是:
var data = await _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_A);
data.AddRange(await _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_B));
data.AddRange(await _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_C));
关于第二次执行;第一次调用(例如 _apiDataService.GetJsonFromApi(apiUrl, apiKey, ENTITY_A))
是 returning 三个对象的列表而不是预期的对象。我允许调试器继续执行以下两个调用 data.AddRange() 和预期的单个对象是 return 从第二次执行的两个对象中编辑并添加到列表中,所以我最终得到五个对象。
有没有人能告诉我我做错了什么。
一些附加信息:
代码在单元测试方法中执行了两次为:
apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();
apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();
传递给GetJsonFromApi
方法的属性都是字符串,而ENTITY_A、ENTITY_B和ENTITY_C是常量,所以在第二次执行时传递给的所有参数功能应该完全一样。
我正在测试应在第二个 运行 上锁定的服务的另一部分,但需要将其正确 运行 以便测试进行。
很可能(在 99% 的情况下)那是因为您正在使用 .Returns()
的重载,它采用现成的值。请注意代码中 .Returns()
的参数是什么:这是一个任务。已经建成。从值构造。已经构建的价值。这是一个列表。
这意味着模拟将记住这个 List<> 对象并在以后重用它。任何时候任何人想要 GetJsonFromApi
给定参数,他们都会得到完全相同的对象实例。你的模拟不会给他们一个具有相似内容的新列表(就像一个正常的 HTTP/etc 客户端的行为),但总是 return 相同的对象实例。
现在,如果您的代码在别处获取该列表并向其追加新项目,会发生什么情况?你的模拟不会注意到。它仍然会很高兴 return 相同的列表实例。现在有更多项目。
我打赌这就是发生的事情。
出于这个原因,.Returns
方法也有一个接受委托的重载:
而不是:foo.Returns(new List<int>{ 1, 2, 3 })
尝试使用:foo.Returns(() => new List<int>{ 1, 2, 3 })
而不是:foo.Returns(Task.FromResult(new List<int>{ 1, 2, 3 }))
尝试使用:foo.Returns(() => Task.FromResult(new List<int>{ 1, 2, 3 }))
甚至:foo.ReturnsAsync(() => new List<int>{ 1, 2, 3 })
这样,模拟缓存的唯一内容就是 lambda,并且在调用模拟方法之前不会执行 lambda。然后,每次调用模拟方法时,都会再次执行 lambda,并且 returns 是一个新构造的对象。如果稍后有任何代码修改了该对象,也没关系,因为下一次调用模拟方法将构建另一个新响应。