为什么在尝试对整个 Spring 批处理作业进行单元测试时出现此错误?没有可用的 'org.springframework.batch.core.Job' 类型的合格 bean
Why this error trying to unit test an entire Spring Batch Job? No qualifying bean of type 'org.springframework.batch.core.Job' available
我正在开发 Spring 批处理应用程序。直到现在我才能够对诸如服务方法之类的东西进行单元测试(就像在每个 Spring 启动应用程序中所做的那样)。
现在我正在尝试按照本教程进行操作,以便从我的单元测试中测试整个作业 class(基本上我想执行一个执行作业的测试方法):https://www.baeldung.com/spring-batch-testing-job
这是我的 JUnit 测试 class,在这种情况下工作正常,我可以使用 @SpringBootTest 注释正确地测试我的服务方法:
@SpringBootTest
@SpringBatchTest
class UpdateInfoBatchApplicationTests {
@Autowired
private NotaryService notaryService;
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@After
public void cleanUp() {
jobRepositoryTestUtils.removeJobExecutions();
}
@Autowired
@Qualifier("launcher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("updateNotaryDistrictsJob")
private Job updateNotaryDistrictsJob;
@Autowired
@Qualifier("updateNotaryListInfoJob")
private Job updateNotaryListInfoJob;
private JobParameters defaultJobParameters() {
JobParametersBuilder paramsBuilder = new JobParametersBuilder();
//paramsBuilder.addString("file.input", TEST_INPUT);
//paramsBuilder.addString("file.output", TEST_OUTPUT);
return paramsBuilder.toJobParameters();
}
@Test
public void givenReferenceOutput_whenJobExecuted_thenSuccess() throws Exception {
// when
JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
JobInstance actualJobInstance = jobExecution.getJobInstance();
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
Assert.assertEquals(actualJobInstance.getJobName(), "updateNotaryDistrictsJob");
// then
//assertThat(actualJobInstance.getJobName(), is("updateNotaryDistrictsJob"));
//assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
//AssertFile.assertFileEquals(expectedResult, actualResult);
}
@Test
void contextLoads() {
System.out.println("TEST - contextLoads()");
}
@Test
void getNotaryList() throws Exception {
List<Notary> notaryList = this.notaryService.getNotaryList();
System.out.println("notaryList size: " + notaryList);
Assert.assertEquals("Notary List must be 5069", 5069, notaryList.size());
}
@Test
void getNotaryDetails() throws Exception {
NotaryDetails notaryDetails = this.notaryService.getNotaryDetails("089cy5Ra9zE%253D");
System.out.println("notaryDetails: " + notaryDetails);
Assert.assertEquals("Notary ID must be 089cy5Ra9zE%253D", "089cy5Ra9zE%253D", notaryDetails.getIdNotary());
}
@Test
void getNotaryDistrictsList() throws Exception {
List<NotaryDistrict> notaryDistrictsList = this.notaryService.getNotaryDistrictsList();
System.out.println("notaryDistrictsList: " + notaryDistrictsList);
Assert.assertEquals("Notary districts list lenght must be 91", 91, notaryDistrictsList.size());
//ArrayList<NotaryDistrict> notaryDistrictsListArrayList = new ArrayList<NotaryDistrict>(notaryDistrictsList);
notaryDistrictsList.remove(0);
Assert.assertEquals("Notary districts list lenght must now be 90", 90, notaryDistrictsList.size());
}
@Test
void getNotaryDistrictDetails() throws Exception {
NotaryDistrictDetails notaryDistrictDetails = this.notaryService.getNotaryDistrictDetails("CG7drXn9fvA%253D");
System.out.println("notaryDistrictDetails: " + notaryDistrictDetails.toString());
Assert.assertEquals("Distretto must be: SCIACCA", "SCIACCA", notaryDistrictDetails.getDistretto());
}
}
正如您在前面的代码中所见,我首先注入了我定义的两个 Job 对象:
@Autowired
@Qualifier("launcher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("updateNotaryDistrictsJob")
private Job updateNotaryDistrictsJob;
这些 Job 被定义为配置我的 Spring 批处理作业和步骤的 class 中的 bean,基本上我有这两个 bean:
@Bean("updateNotaryDistrictsJob")
public Job updateNotaryDistrictsListInfoJob(){
return jobs.get("updateNotaryDistrictsListInfoJob")
.incrementer(new RunIdIncrementer())
.start(readNotaryDistrictsListStep())
.build();
}
和
@Bean("updateNotaryListInfoJob")
public Job updateNotaryListInfoJob(){
return jobs.get("updateNotaryListInfoJob")
.incrementer(new RunIdIncrementer())
.start(readNotaryListStep())
.build();
}
然后在之前的测试中class有这个测试方法应该测试之前updateNotaryDistrictsJob作业的整个流程:
@Test
public void givenReferenceOutput_whenJobExecuted_thenSuccess() throws Exception {
// when
JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
JobInstance actualJobInstance = jobExecution.getJobInstance();
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
Assert.assertEquals(actualJobInstance.getJobName(), "updateNotaryDistrictsJob");
}
问题是,当我运行这个测试方法时,我在我的堆栈跟踪中得到这个异常:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jobLauncherTestUtils': Unsatisfied dependency expressed through method 'setJob' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.batch.core.Job' available: expected single matching bean but found 2: updateNotaryDistrictsJob,updateNotaryListInfoJob
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:768) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:720) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean[=17=](AbstractBeanFactory.java:335) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.3.jar:2.5.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.3.jar:2.5.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.3.jar:2.5.3]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123) ~[spring-boot-test-2.5.3.jar:2.5.3]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.3.9.jar:5.3.9]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-5.3.9.jar:5.3.9]
... 69 common frames omitted
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.batch.core.Job' available: expected single matching bean but found 2: updateNotaryDistrictsJob,updateNotaryListInfoJob
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1358) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:760) ~[spring-beans-5.3.9.jar:5.3.9]
... 88 common frames omitted
好像不能识别我的两个Job bean中必须使用哪个。
为什么?怎么了?我错过了什么?我该如何解决这个问题?
@SpringBatchTest
提供的JobLauncherTestUtils
期望在测试上下文中只有一个Job
类型的bean。这也记录在注释的 java 文档中。
如果您使用 @SpringBootTest
和完整组件扫描,以便拾取多个作业 bean,@SpringBatchTest
无法开箱即用。
最简单的解决方案可能是删除 @SpringBatchTest
并使用 jobLauncher
启动作业。或者,您可以将测试拆分为多个测试 类 并分别使用仅包含单个作业 bean 的测试上下文。
我正在开发 Spring 批处理应用程序。直到现在我才能够对诸如服务方法之类的东西进行单元测试(就像在每个 Spring 启动应用程序中所做的那样)。
现在我正在尝试按照本教程进行操作,以便从我的单元测试中测试整个作业 class(基本上我想执行一个执行作业的测试方法):https://www.baeldung.com/spring-batch-testing-job
这是我的 JUnit 测试 class,在这种情况下工作正常,我可以使用 @SpringBootTest 注释正确地测试我的服务方法:
@SpringBootTest
@SpringBatchTest
class UpdateInfoBatchApplicationTests {
@Autowired
private NotaryService notaryService;
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@After
public void cleanUp() {
jobRepositoryTestUtils.removeJobExecutions();
}
@Autowired
@Qualifier("launcher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("updateNotaryDistrictsJob")
private Job updateNotaryDistrictsJob;
@Autowired
@Qualifier("updateNotaryListInfoJob")
private Job updateNotaryListInfoJob;
private JobParameters defaultJobParameters() {
JobParametersBuilder paramsBuilder = new JobParametersBuilder();
//paramsBuilder.addString("file.input", TEST_INPUT);
//paramsBuilder.addString("file.output", TEST_OUTPUT);
return paramsBuilder.toJobParameters();
}
@Test
public void givenReferenceOutput_whenJobExecuted_thenSuccess() throws Exception {
// when
JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
JobInstance actualJobInstance = jobExecution.getJobInstance();
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
Assert.assertEquals(actualJobInstance.getJobName(), "updateNotaryDistrictsJob");
// then
//assertThat(actualJobInstance.getJobName(), is("updateNotaryDistrictsJob"));
//assertThat(actualJobExitStatus.getExitCode(), is("COMPLETED"));
//AssertFile.assertFileEquals(expectedResult, actualResult);
}
@Test
void contextLoads() {
System.out.println("TEST - contextLoads()");
}
@Test
void getNotaryList() throws Exception {
List<Notary> notaryList = this.notaryService.getNotaryList();
System.out.println("notaryList size: " + notaryList);
Assert.assertEquals("Notary List must be 5069", 5069, notaryList.size());
}
@Test
void getNotaryDetails() throws Exception {
NotaryDetails notaryDetails = this.notaryService.getNotaryDetails("089cy5Ra9zE%253D");
System.out.println("notaryDetails: " + notaryDetails);
Assert.assertEquals("Notary ID must be 089cy5Ra9zE%253D", "089cy5Ra9zE%253D", notaryDetails.getIdNotary());
}
@Test
void getNotaryDistrictsList() throws Exception {
List<NotaryDistrict> notaryDistrictsList = this.notaryService.getNotaryDistrictsList();
System.out.println("notaryDistrictsList: " + notaryDistrictsList);
Assert.assertEquals("Notary districts list lenght must be 91", 91, notaryDistrictsList.size());
//ArrayList<NotaryDistrict> notaryDistrictsListArrayList = new ArrayList<NotaryDistrict>(notaryDistrictsList);
notaryDistrictsList.remove(0);
Assert.assertEquals("Notary districts list lenght must now be 90", 90, notaryDistrictsList.size());
}
@Test
void getNotaryDistrictDetails() throws Exception {
NotaryDistrictDetails notaryDistrictDetails = this.notaryService.getNotaryDistrictDetails("CG7drXn9fvA%253D");
System.out.println("notaryDistrictDetails: " + notaryDistrictDetails.toString());
Assert.assertEquals("Distretto must be: SCIACCA", "SCIACCA", notaryDistrictDetails.getDistretto());
}
}
正如您在前面的代码中所见,我首先注入了我定义的两个 Job 对象:
@Autowired
@Qualifier("launcher")
private JobLauncher jobLauncher;
@Autowired
@Qualifier("updateNotaryDistrictsJob")
private Job updateNotaryDistrictsJob;
这些 Job 被定义为配置我的 Spring 批处理作业和步骤的 class 中的 bean,基本上我有这两个 bean:
@Bean("updateNotaryDistrictsJob")
public Job updateNotaryDistrictsListInfoJob(){
return jobs.get("updateNotaryDistrictsListInfoJob")
.incrementer(new RunIdIncrementer())
.start(readNotaryDistrictsListStep())
.build();
}
和
@Bean("updateNotaryListInfoJob")
public Job updateNotaryListInfoJob(){
return jobs.get("updateNotaryListInfoJob")
.incrementer(new RunIdIncrementer())
.start(readNotaryListStep())
.build();
}
然后在之前的测试中class有这个测试方法应该测试之前updateNotaryDistrictsJob作业的整个流程:
@Test
public void givenReferenceOutput_whenJobExecuted_thenSuccess() throws Exception {
// when
JobExecution jobExecution = jobLauncherTestUtils.launchJob(defaultJobParameters());
JobInstance actualJobInstance = jobExecution.getJobInstance();
ExitStatus actualJobExitStatus = jobExecution.getExitStatus();
Assert.assertEquals(actualJobInstance.getJobName(), "updateNotaryDistrictsJob");
}
问题是,当我运行这个测试方法时,我在我的堆栈跟踪中得到这个异常:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jobLauncherTestUtils': Unsatisfied dependency expressed through method 'setJob' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.batch.core.Job' available: expected single matching bean but found 2: updateNotaryDistrictsJob,updateNotaryListInfoJob
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:768) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:720) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1413) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean[=17=](AbstractBeanFactory.java:335) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.9.jar:5.3.9]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.3.jar:2.5.3]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.3.jar:2.5.3]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.3.jar:2.5.3]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123) ~[spring-boot-test-2.5.3.jar:2.5.3]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99) ~[spring-test-5.3.9.jar:5.3.9]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-5.3.9.jar:5.3.9]
... 69 common frames omitted
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.batch.core.Job' available: expected single matching bean but found 2: updateNotaryDistrictsJob,updateNotaryListInfoJob
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1358) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.9.jar:5.3.9]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:760) ~[spring-beans-5.3.9.jar:5.3.9]
... 88 common frames omitted
好像不能识别我的两个Job bean中必须使用哪个。
为什么?怎么了?我错过了什么?我该如何解决这个问题?
@SpringBatchTest
提供的JobLauncherTestUtils
期望在测试上下文中只有一个Job
类型的bean。这也记录在注释的 java 文档中。
如果您使用 @SpringBootTest
和完整组件扫描,以便拾取多个作业 bean,@SpringBatchTest
无法开箱即用。
最简单的解决方案可能是删除 @SpringBatchTest
并使用 jobLauncher
启动作业。或者,您可以将测试拆分为多个测试 类 并分别使用仅包含单个作业 bean 的测试上下文。