首页 > 解决方案 > Moq 返回设置在第二次执行时返回错误数据

问题描述

我在单元测试中设置返回值时遇到问题,导致在第二次执行时返回意外数据。

我正在测试我创建的服务,并且我有以下代码来设置服务中使用的接口的返回数据。

         _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))返回三个对象的列表而不是预期的。我允许调试器继续使用 data.AddRange() 执行以下两个调用,并且预期的单个对象从第二个对象返回执行并添加到列表中,所以我最终得到了五个对象。

有没有人能告诉我我做错了什么。

一些附加信息:

代码在单元测试方法中执行两次,如下所示:

    apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();
    apiCheckerService.AddEntitiesHash(client.Id).GetAwaiter().GetResult();

传递给GetJsonFromApi方法的属性都是字符串,而 ENTITY_A、ENTITY_B 和 ENTITY_C 是常量,所以在第二次执行时,传递给函数的所有参数应该完全相同。我正在测试应该在第二次运行时锁定的服务的另一部分,但需要它正确运行才能进行测试。

标签: c#unit-testingmoq

解决方案


最有可能(如在 99% 的情况下)那是因为您使用的重载.Returns()需要一个现成的值。请注意代码中的参数是什么.Returns():它是一个任务。已经建成。从一个值构造。已经构建的值。这是一个列表。

这意味着 mock 将记住这个 List<> 对象并在以后重用它。任何时候想要GetJsonFromApi使用给定的参数,它们都会得到相同的对象实例。您的模拟不会为它们提供具有相似内容的新列表(就像普通的 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.Returns(() => Task.FromResult(new List<int>{ 1, 2, 3 }))
甚至:foo.ReturnsAsync(() => new List<int>{ 1, 2, 3 })

这样,mock 缓存的唯一内容就是lambda,并且在调用 mocked 方法之前不会执行 lambda。然后,每次调用模拟方法时,都会再次执行 lambda,并返回一个新构造的对象。如果以后有任何代码修改了该对象,那也没关系,因为下一次调用模拟方法将构建另一个新的响应。


推荐阅读