У меня есть метод, который вызывает статический метод класса Util. Есть ли способ смоделировать возвращаемое значение этого статического метода, чтобы он всегда возвращал статическое значение?
Я хочу сделать это, потому что этот статический метод возвращает мне некоторые конфигурации, которые могут измениться, но я не хочу, чтобы это повлияло на мой тест. Если бы я мог издеваться над этим классом Util и постоянно возвращать статические результаты для метода, это дало бы мне стабильный тест.
public class SomeService{
public void doSth(){
SomeConfig config = UtilClass.getConfigForId(“id”);
...
}
}
Я попробовал mockStatic метод, упомянутый здесь, но он не сработал:
// get SomeService instance from Guice container
Injector injector = Guice.createInjector(new BasicModule());
SomeService someService = injector.getInstance(SomeService.class);
try (MockedStatic<UtilClass> dummy = Mockito.mockStatic(UtilClass.class)) {
dummy.when(() -> UtilClass.getConfigForId(any()))
.thenReturn(someConfig);
// Didn't give me someConfig above, but the actual value
someService.doSth();
}
ОБНОВЛЯТЬ:
Приносим свои извинения за все допущенные опечатки. Теперь я исправил их, чтобы в коде не было синтаксических ошибок. Я думаю, что нашел то, что могло сломать MockedStatic: этот UtilClass.getConfigForId вызывается в другом потоке. Вот общая структура:
public class SomeService{
public void doSth(List<Msg> messages){
List<Callable<Void>> tasks = new ArrayList<>();
for (Msg msg : messages){
tasks.add(()->{
String id = msg.getId();
SomeConfig config = UtilClass.getConfigForId(id);
...
})
}
// use Executors.newWorkStealingPool() to execute the tasks
...
}
}
Пока UtilClass.getConfigForId(id) не выполняется в том же потоке, что и код try with resource, магия не работает. Я пробовал другую функцию, которая делает то же самое, но в одном потоке, и MockedStatic там работает нормально. Таким образом, возникает вопрос: как заставить MockedStatic работать в другом потоке? Еще одно ОБНОВЛЕНИЕ: Я думаю, что из javaDoc это невозможно. Поэтому мне придется придумать другие способы, например. рефакторинг
@XtremeBaumer прав - тестовый код даже не компилируется (несовместимые типы).
Извините за опечатку @XtremeBaumer. Я исправил их. И я, кажется, выяснил, почему у меня не работает MockedStatic: что он вызывается другим потоком.
Ваш протестированный код не соответствует вашему тестовому коду. В SomeService#doSth вы вызываете UtilClass.getConfigForId(), но в тесте вы издеваетесь над методом с другой сигнатурой: UtilClass.getConfigForId("id").
Если вы используете в коде метод со строковым параметром, заглушка должна ему соответствовать, поэтому, если в тесте вы ожидаете значение "id", оно должно быть передано и методу в тестируемом коде (SomeService) (значения сравниваются используя метод equals в Mockito по умолчанию). В противном случае вы должны использовать ArgumentMatcher, как и any().
var id = "id";
var mockedValue = "test value";
var someConfig = new SomeConfig(mockedValue);
var someService = new SomeService();
try (MockedStatic<UtilClass> dummy = Mockito.mockStatic(UtilClass.class)) {
dummy.when(() -> UtilClass.getConfigForId(id))
.thenReturn(someConfig);
var result = someService.doSth(id);
assertEquals(mockedValue, result);
}
var mockedValue = "test value 2";
var someConfig = new SomeConfig(mockedValue);
var someService = new SomeService();
try (MockedStatic<UtilClass> dummy = Mockito.mockStatic(UtilClass.class)) {
dummy.when(() -> UtilClass.getConfigForId(any()))
.thenReturn(someConfig);
var result = someService.doSth("some id");
assertEquals(mockedValue, result);
}
Если сигнатура метода, который вы используете в тестируемом коде, не включает параметр, он также должен быть имитирован без параметра (например, в случае перегруженного метода).
Я протестировал приведенный выше код, и все тесты проходят — вы можете проверить это в репозитории GitHub.
Спасибо за отличный ответ и код @Jonasz. Извиняюсь за опечатку в моем вопросе - я дважды проверил и могу убедиться, что подписи и значения параметров совпадают в моем коде. Так что это все еще не работает для меня. Будет ли это иметь какое-то отношение к тому, что мой someService в тесте управляется Guice (что-то вроде Spring)?
Не могли бы вы сделать так, чтобы код в вашем вопросе максимально отражал ваш фактический код (включая весь метод тестирования)? К сожалению, трудно предположить, но я не думаю, что инструмент внедрения зависимостей, такой как Guice, вызовет проблемы с mockStatic, поэтому я полагаю, что это может быть что-то другое.
Йонаш Я обновил вопрос. Вы правы, инструменту инъекций нечего делать, но многопоточность делает. Вы не против проверить мое обновление?
Однако вы правы в своем последнем обновлении - имитирующие статические методы следует использовать только тогда, когда у вас нет контроля над тестируемым кодом, в противном случае лучше избегать таких статических методов, поскольку они значительно усложняют тестирование кода. Было бы лучше использовать DI (поскольку вы уже используете Guice), чтобы внедрить класс, создающий/загружающий конфигурацию, и заменить его в тесте макетом, над которым у вас будет полный контроль. Класс поставщика конфигурации также должен иметь свои собственные тесты.
В опубликованном вами коде вы издеваетесь над классом обслуживания, а не над классом утилиты Mockito.mockStatic(SomeService.class)