Использование @MockBean в тестах вызывает перезагрузку контекста приложения

У меня есть несколько интеграционных тестов, запущенных на Spring Framework, которые расширяют базовый класс под названием BaseITCase.
Примерно так:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppCacheConfiguration.class, TestConfiguration.class}, loader = SpringBootContextLoader.class)
@Transactional
@WebMvcTest
public abstract class BaseITCase{...}
...
public class UserControllerTest extends BaseITCase {...}

Проблема в том, что в одном из тестов есть несколько объявлений: @MockBean внутри, и в момент выполнения этого теста Spring воссоздает контекст, а тесты, следующие за этим, иногда используют неправильные bean-компоненты (из контекста, созданного именно для теста с помощью @ MockBean). Я узнал об этом, просто проверив, что у bean-компонентов разные хэш-коды.

Когда я использую @EventListener, это становится действительно критичным. Потому что вызываются слушатели для неправильного контекста (контекст тестового класса, который уже завершил выполнение), и у меня там неправильные bean-компоненты.

Есть ли обходной путь для этого?

Я попытался переместить все объявления @MockBean в базовый класс, и это сработало нормально, потому что новый контекст не создается. Но это делает базовый класс слишком тяжелым. Кроме того, я попытался создать грязный контекст для этого теста, но затем следующий тест завершился неудачно с сообщением о том, что контекст уже закрыт.


person dvelopp    schedule 09.08.2017    source источник
comment
По ошибке в вашем посте был опубликован ответ на другой вопрос. Прости! Я удалил это.   -  person Perimosh    schedule 22.02.2018


Ответы (2)


Причина в том, что конфигурация Spring для теста с @MockBean отличается от остальных тестов, поэтому инфраструктура Spring не может кэшировать ранее использованный контекст и должна загружать его снова. Здесь вы можете найти более подробное объяснение: https://github.com/spring-projects/spring-boot/issues/10015

Как вы сказали, если вы переместите фиктивный bean-компонент в родительский класс, контекст не будет перезагружен, что имеет смысл, поскольку конфигурация bean-компонента остается прежней.

Возможный обходной путь - определить ваш mock bean как простой mock и ввести его вручную там, где это необходимо.

Например, UserController зависит от Foo:

public class UserControllerTest extends BaseITCase {

    private Foo foo = Mockito.mock(Foo.class);

    @Autowired
    private UserController userController;

    @Before
    public void setUp() {
        super.setup();

        this.userController.setFoo(foo);
    }
}

@Component
public class UserController {

    private Foo foo;

    @Autowired
    public void setFoo(final Foo foo) {
        this.foo = foo;
    }
}

Надеюсь это поможет.

person Daniel Camarasa    schedule 26.02.2018

@MockBean может вызвать перезагрузку контекста , как описано в предыдущем ответе.

В качестве альтернативы, если вы используете Spring boot 2.2+, вы можете использовать @MockInBean вместо @MockBean. Он сохраняет ваш контекст чистым и не требует перезагрузки контекста.

@SpringBootTest
public class UserControllerTest extends BaseITCase {

    @MockInBean(UserController.class)
    private Foo foo;

    @Autowired
    private UserController userController;

    @Test
    public void test() {
        userController.doSomething();
        Mockito.verify(foo).hasDoneSomething();
    }
}

@Component
public class UserController {

    @Autowired
    private Foo foo;

}

отказ от ответственности: я создал эту библиотеку именно для этой цели: имитировать beans в Spring beans и избежать длительного воссоздания контекста.

person Antoine Meyer    schedule 18.03.2021