Pav*_*vlo 5 java testing spring atomikos spring-boot
如何创建适当的测试环境,以便能够在同一应用程序中使用数据库层测试和带有模拟的 REST 端点测试?
我有一个带有两个数据源的 Spring Boot 应用程序。用于管理 Atomikos 使用的交易。这个配置工作正常。
现在我需要创建测试。我构建了一个测试配置,每个测试都工作正常,但当我运行所有测试时它都会失败。在我看来(参见堆栈跟踪),问题是如果实例化很少的 Atomikos beans,Atomikos 就无法工作。
我尝试了两种解决方案来使 Atomikos beans 仅实例化一次:
创建一个用于所有测试的测试配置(因为 Spring 缓存测试上下文)。但这不起作用。我认为这是因为 @Controller 中的 Mock beans 破坏了重用 Spring 测试上下文的能力。我在测试中使用的持久性映射器组件在一个测试中被模拟,同时在其他测试中使用真实实例。所以我看到每个测试类都在它自己的测试上下文中运行。
在数据库 @Configuration 类上使用 @Lazy 注释。我认为这将确保 bean 仅在第一次调用时才会被实例化,并将在以后的调用中重用。但这也行不通。
这是我用来说明问题的示例项目链接。该存储库包括 MySQL 数据库转储: https: //github.com/pavelmorozov/AtomikosConfig
在这篇文章中,我将仅展示两个数据库模型、映射器和配置类中的一个,因为它们对于第二个数据库几乎相同。
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:539)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:761)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:461)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:207)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoController': Unsatisfied dependency expressed through field 'firstMapper'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstMapper' defined in file [/home/pm/Documents/workspace-sts-3.9.1.RELEASE/AtomikosConfig/target/classes/com/example/demo/persistence/mapper/first/FirstMapper.class]: Cannot resolve reference to bean 'firstSqlSessionFactory' while setting bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'firstSqlSessionFactory' defined in class path resource [com/example/demo/persistence/configuration/FirstDatabaseConfiguration.class]: Unsatisfied dependency expressed through method 'firstSqlSessionFactory' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstDataSource' defined in class path resource [com/example/demo/persistence/configuration/FirstDatabaseConfiguration.class]: Invocation of init method failed; nested exception is com.atomikos.jdbc.AtomikosSQLException: Cannot initialize AtomikosDataSourceBean
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
... 25 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstMapper' defined in file [/home/pm/Documents/workspace-sts-3.9.1.RELEASE/AtomikosConfig/target/classes/com/example/demo/persistence/mapper/first/FirstMapper.class]: Cannot resolve reference to bean 'firstSqlSessionFactory' while setting bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'firstSqlSessionFactory' defined in class path resource [com/example/demo/persistence/configuration/FirstDatabaseConfiguration.class]: Unsatisfied dependency expressed through method 'firstSqlSessionFactory' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstDataSource' defined in class path resource [com/example/demo/persistence/configuration/FirstDatabaseConfiguration.class]: Invocation of init method failed; nested exception is com.atomikos.jdbc.AtomikosSQLException: Cannot initialize AtomikosDataSourceBean
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1531)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1276)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 43 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'firstSqlSessionFactory' defined in class path resource [com/example/demo/persistence/configuration/FirstDatabaseConfiguration.class]: Unsatisfied dependency expressed through method 'firstSqlSessionFactory' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstDataSource' defined in class path resource [com/example/demo/persistence/configuration/FirstDatabaseConfiguration.class]: Invocation of init method failed; nested exception is com.atomikos.jdbc.AtomikosSQLException: Cannot initialize AtomikosDataSourceBean
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:467)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1173)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1067)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 56 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'firstDataSource' defined in class path resource [com/example/demo/persistence/configuration/FirstDatabaseConfiguration.class]: Invocation of init method failed; nested exception is com.atomikos.jdbc.AtomikosSQLException: Cannot initialize AtomikosDataSourceBean
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
... 66 more
Caused by: com.atomikos.jdbc.AtomikosSQLException: Cannot initialize AtomikosDataSourceBean
at com.atomikos.jdbc.AtomikosSQLException.throwAtomikosSQLException(AtomikosSQLException.java:46)
at com.atomikos.jdbc.AbstractDataSourceBean.init(AbstractDataSourceBean.java:306)
at org.springframework.boot.jta.atomikos.AtomikosDataSourceBean.afterPropertiesSet(AtomikosDataSourceBean.java:49)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
... 77 more
Caused by: javax.naming.NamingException: Another resource already exists with name firstDataSource - pick a different name
at com.atomikos.util.IntraVmObjectFactory.createReference(IntraVmObjectFactory.java:94)
at com.atomikos.jdbc.AbstractDataSourceBean.getReference(AbstractDataSourceBean.java:388)
at com.atomikos.jdbc.AbstractDataSourceBean.init(AbstractDataSourceBean.java:295)
... 80 more
Run Code Online (Sandbox Code Playgroud)
。
@SpringBootApplication
@EnableAutoConfiguration(
exclude = {DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
MybatisAutoConfiguration.class})
public class AtomikosConfigApplication {
public static void main(String[] args) {
SpringApplication.run(AtomikosConfigApplication.class, args);
}
}
Run Code Online (Sandbox Code Playgroud)
。
@Configuration
@Lazy
public class FirstDatabaseConfiguration {
@Bean
public MapperScannerConfigurer firstMapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.example.demo.persistence.mapper.first");
configurer.setSqlSessionFactoryBeanName("firstSqlSessionFactory");
return configurer;
}
/**
* This bean uses Atomikos
* to get transaction atomicity for
* few data sources (distributed transaction)
*/
@Bean
@ConfigurationProperties(prefix = "spring.jta.atomikos.datasource.first")
public DataSource firstDataSource() {
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setPoolSize(10);
atomikosDataSourceBean.setMaxLifetime(3600);
return atomikosDataSourceBean;
}
@Bean
public SqlSessionFactory firstSqlSessionFactory(
@Qualifier("firstDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
}
Run Code Online (Sandbox Code Playgroud)
。
@Mapper
public interface FirstMapper {
@Select("SELECT * from first WHERE id = #{id}")
@Results(value = {
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name")
})
FirstModel selectById(@Param("id") long id);
}
Run Code Online (Sandbox Code Playgroud)
。
public class FirstModel {
private long id;
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Run Code Online (Sandbox Code Playgroud)
。
@RestController
public class DemoController {
@Autowired
FirstMapper firstMapper;
@Autowired
SecondMapper secondMapper;
@GetMapping("/demo-controller")
@Transactional
public String getDemoData() {
String firstName = firstMapper.selectById(1).getName();
String secondName = secondMapper.selectById(1).getName();
String response = "{\"first\":"+firstName+", \"secondName\":"+secondName+"}";
return response;
}
}
Run Code Online (Sandbox Code Playgroud)
。
@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, MybatisAutoConfiguration.class })
@AutoConfigureMockMvc
public class DemoControllerTest {
@MockBean
FirstMapper firstMapper;
@MockBean
SecondMapper secondMapper;
@Autowired
MockMvc mvc;
@Test
public void getDemoDataTest() throws Exception {
FirstModel firstModel = new FirstModel();
firstModel.setId(1);
firstModel.setName("first name");
given(firstMapper.selectById(1l)).willReturn(firstModel);
SecondModel secondModel = new SecondModel();
secondModel.setId(1);
secondModel.setName("second name");
given(secondMapper.selectById(1l)).willReturn(secondModel);
mvc.perform(get("/demo-controller").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andExpect(jsonPath("$.first", is("first name")));
}
}
Run Code Online (Sandbox Code Playgroud)
。
@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAutoConfiguration(
exclude = {DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
MybatisAutoConfiguration.class
})
public class FirstMapperTest {
@Autowired
FirstMapper firstMapper;
@Test
public void selectByIdTest() {
FirstModel first = firstMapper.selectById(1);
assertEquals("first name", first.getName());
}
}
Run Code Online (Sandbox Code Playgroud)
所以我遇到了这个问题,在网上也找不到解决方案。这是我终于开始工作的:
我从 spring-boot-autoconfigure 复制了很多 AtomikosJtaConfiguration ,因为自动配置内容没有被纳入我的测试中。我发现在运行集成测试时,Spring 上下文被刷新了很多次,并且在某些(但不是全部)刷新中,@Bean 方法被调用来重新创建 bean。数据源方法失败,因为以前的数据源由于某种原因没有被清理,所以我决定使用静态变量来保存数据源,并且每次都返回相同的数据源。剩下的部分就起作用了。您应该做的另一件事是设置 Atomikos 在测试期间不写入事务日志,以便测试可以并行运行而不会相互干扰。
@Bean
public DataSource dataSource() throws Exception {
if (ATOMIKOS_DATA_SOURCE_BEAN == null) {
if (xaDataSource == null) {
xaDataSource = createXaDataSource();
}
ATOMIKOS_DATA_SOURCE_BEAN = new AtomikosXADataSourceWrapper().wrapDataSource(xaDataSource);
}
return ATOMIKOS_DATA_SOURCE_BEAN;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2543 次 |
| 最近记录: |