Область действия Spring bean для «одного объекта на метод тестирования»

У меня есть тестовая утилита, для которой мне нужен новый экземпляр для каждого метода тестирования (чтобы предотвратить утечку этого состояния между тестами). До сих пор я использовал область «прототип», но теперь я хочу иметь возможность подключить утилиту к другой тестовой утилите, и подключенные экземпляры должны быть одинаковыми для каждого теста.

Кажется, это стандартная проблема, поэтому мне было интересно, есть ли область «метода тестирования» или что-то подобное?

Это структура тестового класса и тестовых утилит:

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {

    @Autowired
    private TestDriver driver;

    @Autowired
    private TestStateProvider state;

    // ... state
    // ... methods
}
@Component
@Scope("prototype") // not right because MyTest and TestStateProvider get separate instances
public class TestDriver {
    // ...
}
@Component
public class TestStateProvider {

    @Autowired
    private TestDriver driver;

    // ...
}

Я знаю, что мог бы использовать @Scope("singleton") и @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD), но это обновляет больше, чем мне нужно — нового экземпляра TestDriver для каждого теста будет достаточно. Кроме того, этот подход подвержен ошибкам, потому что все тесты, использующие TestDriver, должны знать, что им также нужна аннотация @DirtiesContext. Поэтому я ищу лучшее решение.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
719
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Некоторое время назад я столкнулся с той же проблемой и пришел к следующему решению:

  1. Используйте макеты
  2. Я написал несколько методов для создания конкретных настроек mockito, чтобы добавить поведение к каждому макету.

Поэтому создайте класс TestConfiguration со следующими методами и определением bean-компонента.

    private MockSettings createResetAfterMockSettings() {
        return MockReset.withSettings(MockReset.AFTER);
    }

    private <T> T mockClass(Class<T> classToMock) {
        return mock(classToMock, createResetAfterMockSettings());
    }

и ваше определение компонента будет выглядеть так:

@Bean
public TestDriver testDriver() {
    return mockClass(TestDriver .class);
}

MockReset.AFTER используется для сброса макета после запуска метода тестирования.

И, наконец, добавьте TestExecutionListeners в свой тестовый класс:

@TestExecutionListeners({ResetMocksTestExecutionListener.class})

Я вижу, как это будет работать, если я захочу сбросить состояние макетов (например, счетчик вызовов метода), но TestDriver — это реальный класс реализации, и я хочу сбросить состояние в этой реализации. Это не сработает, не так ли?

oberlies 07.06.2019 15:49
Ответ принят как подходящий

На самом деле довольно легко реализовать testMethod область видимости:

public class TestMethodScope implements Scope {
    public static final String NAME = "testMethod";

    private Map<String, Object> scopedObjects = new HashMap<>();
    private Map<String, Runnable> destructionCallbacks = new HashMap<>();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if (!scopedObjects.containsKey(name)) {
            scopedObjects.put(name, objectFactory.getObject());
        }
        return scopedObjects.get(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        destructionCallbacks.put(name, callback);
    }

    @Override
    public Object remove(String name) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getConversationId() {
        return null;
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    public static class TestExecutionListener implements org.springframework.test.context.TestExecutionListener {

        @Override
        public void afterTestMethod(TestContext testContext) throws Exception {
            ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext) testContext
                    .getApplicationContext();
            TestMethodScope scope = (TestMethodScope) applicationContext.getBeanFactory().getRegisteredScope(NAME);

            scope.destructionCallbacks.values().forEach(callback -> callback.run());

            scope.destructionCallbacks.clear();
            scope.scopedObjects.clear();
        }
    }

    @Component
    public static class ScopeRegistration implements BeanFactoryPostProcessor {

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
            factory.registerScope(NAME, new TestMethodScope());
        }
    }

}

Просто зарегистрируйте прослушиватель выполнения теста, и для каждого теста будет один экземпляр всех @Scope("testMethod") аннотированных типов:

@RunWith(SpringRunner.class)
@SpringBootTest
@TestExecutionListeners(listeners = TestMethodScope.TestExecutionListener.class, 
        mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public class MyTest {

    @Autowired
    // ... types annotated with @Scope("testMethod")

}

Другие вопросы по теме