#java #unit-testing #junit #mockito
#Ява #модульное тестирование #джунит #мокито
Вопрос:
В настоящее время я работаю над лямбдой AWS с использованием Java 11. Для реализации обработчика мне требуется пустой конструктор. Мой куратор выглядит так
public class ApiKeyHandler { private final SecretsManagerClient secretsManagerClient; public ApiKeyHandler() { secretsManagerClient = DependencyFactory.secretsManagerClient(); } public void handleRequest(Object event, Context context) { //Other codes here secretsManagerClient.getSecret(/../); } }
И Класс Фабрики зависимостей
public class DependencyFactory { private DependencyFactory() {} /** * @return an instance of SecretsManagerClient */ public static SecretsManagerClient secretsManagerClient() { return SecretsManagerClient.builder() .region(/**/) .build(); } }
Теперь, когда я пытаюсь написать модульный тест для этого, я не могу имитировать объекты в конструкторе. Есть ли способ, которым я могу посмеяться над этим?
Я пытался
@Mock SecretsManagerClient secretsManagerClient; @InjectMocks ApiKeyHandler handler;
но не повезло. Спасибо
Ответ №1:
Похоже, у вас есть несколько вариантов:
- Вы можете добавить другой конструктор с параметрами для ввода. Это легко и чисто с точки зрения тестирования, но в конце концов у вас будет рабочий код (в данном случае этот конструктор), который используется только для тестов. В целом я не сторонник такого подхода, хотя понимаю, что здесь существуют технологические ограничения.
- Вы можете издеваться над Фабрикой зависимостей. Поскольку вызов статичен, вы можете в конечном итоге использовать PowerMock / PowerMockito, которые на самом деле могут имитировать статические вызовы. Это то, что может оказаться действительно болезненным для поддержания, в целом в наши дни такой подход не рекомендуется.
- Вы можете переписать DependencyFactory так, чтобы он мог быть настроен с помощью какой-либо макетной реализации (которая позволит указать макетные зависимости).:
public interface DependencyFactoryMode { SecretsManagerClient secretsManagerClient(); } public class RealDependencyFactoryMode implements DependencyFactoryMode { public SecretsManagerClient secretsManagerClient() { return SecretsManagerClient.builder() .region(/**/) .build(); } } // in src/test/java - test code in short public class DependencyFactoryTestMode implements DependencyFactoryMode { private SecretsManagerClient smc = Mockito.mock(SecretsManagerClient.class); public SecretsManagerClient secretsManagerClient() { return smc; } // this will be used in tests public SecretsManagerClient getSmcMock() {return smc;} } public class DependencyFactory { private static DependencyFactoryMode mode; static { // depending on the configuration, external properties or whatever // initialize in production mode or test mode // of course this is the most "primitive" implementation you can probably // do better if(isTest) { mode = new TestDependencyFactoryTestMode(); } else { // this is a default behavior mode = new RealDependencyFactoryMode(); } } private DependencyFactory() {} public static DependencyFactoryMode getMode() { return mode; } public static SecretsManagerClient secretsManagerClient() { return mode.secretsManagerClient(); } }
При таком подходе вам придется предварительно настроить фабрику зависимостей таким образом, чтобы во время выполнения теста она «знала», что должна работать в тестовом режиме.
public class Test { @Test public void test() { // DependencyFactoryMode will be used in the test mode DependecyFactoryMode testMode = DependencyFactory.getMode(); var smc = testMode.secretsManagerClient(); Mockito.when(smc.foo()).thenReturn(...); } }
Теперь этот подход страдает тем же недостатком, что и «1», но, по крайней мере, у вас есть код «только для тестов» только на заводе, а не во всех лямбда-функциях (я предполагаю, что у вас их много, иначе, вероятно, первый подход будет наименьшим из всех зол).
Другим возможным недостатком является то, что один и тот же экземпляр DependencyFactory
(с общим статическим режимом насмешек) будет использоваться совместно между тестами, поэтому вы можете в конечном итоге «сбросить» все соответствующие насмешки после теста.
Опять же, все это осложнения, потому что в той форме, которую вы представили, нет способа внедрить зависимость в конструктор из-за ограничения технологии.
Ответ №2:
Добавьте второй конструктор, который принимает параметры:
public ApiKeyHandler(SecretsManagerClient client) { secretsManagerClient = client; } public ApiKeyHandler() { this(DependencyFactory.secretsManagerClient()); }