Spring Boot 3.2.0测试:如何Stub LocalContainerEntityManagerFactoryBean的destroy方法?
我开发的Spring Boot v3.2.0应用中有一个LocalContainerEntityManagerFactoryBean类型的Bean,想在测试运行时让它的父类AbstractEntityManagerFactoryBean的destroy()方法不执行任何操作。
我尝试用以下代码实现:
import static org.mockito.Mockito.doNothing; // 其他导入省略 @SpringBootTest public class MyIntegrationTests { @SpyBean private AbstractEntityManagerFactoryBean entityManagerFactoryBean; @Test void myIntegrationTest() { doNothing().when(entityManagerFactoryBean).destroy(); // 测试剩余代码省略 } }
但运行测试时出现错误,说明没成功创建Spy:
org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock!
就算这个方案能成,也没法在其他集成测试类里便捷复用(只能复制粘贴),想问有没有其他方法在测试时Stub这个Bean的destroy方法?
可行解决方案
方案1:自定义Bean后置处理器(全局复用)
写一个Bean后置处理器,在Bean初始化完成后用Mockito Spy包装原Bean,同时Stub掉destroy()方法。只需写一次,所有测试类自动生效。
import org.mockito.Mockito; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.stereotype.Component; @Component public class EntityManagerFactoryDestroyStubProcessor implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof AbstractEntityManagerFactoryBean) { AbstractEntityManagerFactoryBean spyBean = Mockito.spy((AbstractEntityManagerFactoryBean) bean); Mockito.doNothing().when(spyBean).destroy(); return spyBean; } return bean; } }
将这个类放在src/test/java的对应包下,确保只在测试环境生效,Spring会自动扫描并应用该处理器。
方案2:测试基类(按需复用)
创建一个测试基类,把Spy和Stub逻辑封装进去,其他测试类直接继承即可复用:
import org.mockito.Mockito; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.junit.jupiter.api.BeforeEach; @SpringBootTest public abstract class BaseIntegrationTest { @SpyBean private LocalContainerEntityManagerFactoryBean entityManagerFactoryBean; @BeforeEach void setupEntityManagerFactoryStub() { // 针对父类的destroy方法做Stub Mockito.doNothing().when((AbstractEntityManagerFactoryBean) entityManagerFactoryBean).destroy(); } }
测试类继承基类即可:
public class MyIntegrationTests extends BaseIntegrationTest { @Test void myIntegrationTest() { // 测试逻辑正常编写,destroy方法已被Stub } }
注:直接Spy子类LocalContainerEntityManagerFactoryBean可以避免类型匹配问题,再转型为父类Stub目标方法。
方案3:重写Bean(彻底替换)
在测试环境中直接替换原有的LocalContainerEntityManagerFactoryBean,重写destroy()方法为空实现:
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import org.springframework.orm.jpa.JpaVendorAdapter; @Configuration public class TestEntityManagerFactoryConfiguration { @Primary @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean( DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean() { @Override public void destroy() { // 空实现,不执行任何销毁逻辑 } }; // 复制原Bean的核心配置,保证测试逻辑正常运行 bean.setDataSource(dataSource); bean.setJpaVendorAdapter(jpaVendorAdapter); bean.setPackagesToScan("com.your.app.package"); return bean; } }
这种方法需要确保复制原Bean的所有必要配置,适合对Bean配置完全可控的场景。
内容的提问来源于stack exchange,提问作者Dónal

