Moq return setup возвращает неверные данные при втором выполнении - PullRequest
2 голосов
/ 30 марта 2020

У меня проблема с настройкой возвращаемого значения в моем модульном тесте, из-за которой неожиданные данные возвращаются при втором выполнении.

Я тестирую созданную мной службу, и у меня есть следующий код для настройки вернуть данные из интерфейса, используемого в сервисе.

         _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 являются константами, поэтому при втором выполнении все параметры, передаваемые в функцию, должны быть точно такими же. Я тестирую другую часть службы, которая должна быть заблокирована при втором запуске, но для правильной работы теста она должна быть выполнена.

1 Ответ

3 голосов
/ 30 марта 2020

Скорее всего (как в 99% случаев), потому что вы используете перегрузку .Returns(), которая принимает готовое к использованию значение. Обратите внимание, каковы параметры .Returns() в вашем коде: это задача. Уже построено. Построен из значения. Значение, которое уже построено. И это List.

Это означает, что макет запомнит этот самый объект List <> и будет использовать его позже. В любое время, когда кто-либо захочет GetJsonFromApi с данными параметрами, он получит тот же экземпляр объекта. Ваш макет не даст им новый список с похожим содержимым (как обычно ведет себя обычный клиент HTTP / et c), но всегда будет возвращать один и тот же экземпляр объекта.

Теперь, что произойдет, если ваш код в другом месте получить этот список и добавить в него новые элементы? Твоя насмешка не заметит. Это все еще счастливо возвратило бы тот же самый экземпляр списка. Теперь с большим количеством предметов.

Могу поспорить, что именно так и происходит.

По этой причине метод .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 })

Таким образом, единственная вещь, кешируемая макетом, это лямбда , и лямбда не выполняется, пока не будет вызван смоделированный метод. Затем каждый раз, когда вызывается смоделированный метод, лямбда выполняется снова и возвращает только что созданный объект. Если какой-либо код позже изменяет этот объект, это не имеет значения, поскольку следующий вызов mocked-метода создаст другой ответ fre sh.

...