我在单元测试中设置返回值时遇到问题,导致在第二次执行时返回意外数据。
我正在测试我创建的一个服务,我有以下代码来设置服务中使用的接口的返回数据。
_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
}
}));
以上内容在单元测试中第一次执行我的服务时效果良好,每次调用都返回一个对象。服务中的代码是:
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))
返回了一个由三个对象组成的列表,而不是预期的一个。我允许调试器继续执行以下两个带有数据的调用。AddRange((和预期的单个对象在第二次执行时从这两个调用中返回并添加到列表中,所以我最终得到了五个对象。
有人能告诉我我做错了什么吗。
一些附加信息:
该代码在单元测试方法中执行两次,如下所示:
apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();
apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();
传递给GetJsonFromApi
方法的属性都是字符串,ENTITY_A、ENTITY_B和ENTITY_C是常量,因此在第二次执行时,传递给函数的所有参数应该完全相同。我正在测试服务的另一部分,该部分应该在第二次运行时锁定,但需要正确运行才能进行测试。
很可能(在99%的情况下(,这是因为您使用了.Returns()
的重载,该重载需要一个现成的值。请注意代码中.Returns()
的参数是什么:它是一个Task。已建成。由值构造。已构造的值。这是一份清单。
这意味着mock将记住这个列表<>对象,稍后将重用它。任何时候,只要有任何东西想要用给定的参数GetJsonFromApi
,它们就会得到相同的对象实例。您的mock不会给它们一个具有类似内容的新列表(就像普通的HTTP/etc客户端一样(,但总是返回相同的对象实例。
现在,如果您在其他地方的代码获得该列表并向其添加新项目,会发生什么?你的模拟不会注意到。它仍然很乐意返回相同的列表实例。现在有更多项目。
我敢打赌,事情就是这样。
因此,.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.ReturnsAsync(() => new List<int>{ 1, 2, 3 })
而不是:foo.Returns(Task.FromResult(new List<int>{ 1, 2, 3 }))
这样,mock缓存的唯一东西就是lambda,并且在调用mock方法之前不会执行lambda。然后,每次调用模拟方法时,lambda都会再次执行,并返回一个新构建的对象。若以后有任何代码修改了那个对象,那个也没关系,因为下一次调用模拟方法将构建另一个新的响应。