I have a Spring Boot Batch application that I'm writing integration tests against. However, I'm getting the following error about the EntityManagerFactoryBuilder bean missing when running a test:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'entityManagerFactory' defined in com.example.DatabaseConfig: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder]: :
No qualifying bean of type [org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749) ~[spring-beans-4.2.4.RELEASE.jar:4.2.4.RELEASE]
My understanding is that Spring Boot provides the EntityManagerFactoryBuilder bean on application startup. How can I have the EntityManagerFactoryBuilder provided when running tests?
Here's my test code:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {DatabaseConfig.class, BatchConfiguration.class})
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
StepScopeTestExecutionListener.class })
public class StepScopeTestExecutionListenerIntegrationTests {
#Autowired
private FlatFileItemReader<Foo> reader;
#Rule
public TemporaryFolder testFolder = new TemporaryFolder();
public StepExecution getStepExection() {
StepExecution execution = MetaDataInstanceFactory.createStepExecution();
return execution;
}
#Test
public void testGoodData() throws Exception {
//some test code
}
Here's the DatabaseConfig class:
#Configuration
#EnableJpaRepositories(basePackages={"com.example.repository"},
entityManagerFactoryRef="testEntityManagerFactory",
transactionManagerRef = "testTransactionManager")
public class DatabaseConfig {
#Bean
public LocalContainerEntityManagerFactoryBean testEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource())
.packages("com.example.domain")
.persistenceUnit("testLoad")
.build();
}
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public PlatformTransactionManager testTransactionManager(EntityManagerFactory testEntityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(testEntityManagerFactory);
return transactionManager;
}
}
When (integration) testing a Spring Boot application that is what you should do. The #SpringApplicationConfiguration is intended to take your application class (the one with the #SpringBootApplication annotation) as it will then be triggered to do much of the same auto configuration as a regular Spring Boot application.
You are only including 2 configuration classes and as such no auto configuration will be done.
Related
I have a Spring Batch Classifier to test for which I've defined this test class:
#RunWith(SpringRunner.class)
#SpringBatchTest
#ContextConfiguration(classes = { BatchConfiguration.class })
class CsvOutputClassifierTest {
#Autowired
private FlatFileItemWriter<CsvData> createRequestForProposalWriter;
#Autowired
private FlatFileItemWriter<CsvData> createRequestForQuotationWriter;
private final CsvOutputClassifier csvOutputClassifier = new CsvOutputClassifier(
createRequestForProposalWriter,
createRequestForQuotationWriter);
#Test
void shouldReturnProposalWriter() {
...
}
The batch configuration class has this constructor:
public BatchConfiguration(
final JobBuilderFactory jobBuilderFactory,
final StepBuilderFactory stepBuilderFactory,
#Qualifier("oerationalDataSource") final DataSource oerationalDataSource,
final DwhFileManager dwhFileManager,
final OperationalRepository operationalRepository)
And these beans:
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForProposalWriter(
#Value("#{jobParameters['startDate']}") String startDate) {
FlatFileItemWriter<CsvData> writer = new FlatFileItemWriter<CsvData>();
...
return writer;
}
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForQuotationWriter(
#Value("#{jobParameters['startDate']}") String startDate) {
FlatFileItemWriter<CsvData> writer = new FlatFileItemWriter<CsvData>();
...
return writer;
}
Running the test class I'm not able to trigger the first test method as I'm getting:
Error creating bean with name 'batchConfiguration': Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Qualifier(value="oerationalDataSource")}
In fact, I defined two different data sources, one for the 'operational' data and the 'app' for Spring Batch persistency:
#Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {
#Bean
#Primary
#ConfigurationProperties("app.datasource")
public DataSourceProperties defaultDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.configuration")
public HikariDataSource defaultDataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class)
.build();
}
#Bean
#ConfigurationProperties("aggr.datasource")
public DataSourceProperties oerationalDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("aggr.datasource.configuration")
public HikariDataSource oerationalDataSource(
#Qualifier("oerationalDataSourceProperties") DataSourceProperties oerationalDataSourceProperties) {
return oerationalDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean
public JdbcTemplate operationalJdbcTemplate(#Qualifier("oerationalDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
In #SpringBatchTest documentation it is reported that just one DataSource should be found or it should be marked as Primary:
It should be noted that JobLauncherTestUtils requires a org.springframework.batch.core.Job bean and that JobRepositoryTestUtils requires a javax.sql.DataSource bean. Since this annotation registers a JobLauncherTestUtils and a JobRepositoryTestUtils in the test context, it is expected that the test context contains a single autowire candidate for a org.springframework.batch.core.Job and a javax.sql.DataSource (either a single bean definition or one that is annotated with org.springframework.context.annotation.Primary).
But I have it. So how to fix it?
Update #1
Thanks to #Henning's tip, I've changed the annotations as follows:
#RunWith(SpringRunner.class)
#SpringBatchTest
#SpringBootTest(args={"--mode=custom", "--startDate=2022-05-31T01:00:00.000Z", "--endDate=2022-05-31T23:59:59.999Z"})
#ContextConfiguration(classes = { BatchConfiguration.class, DataSourceConfiguration.class, LocalFileManager.class, AggregatorRepository.class })
#ActiveProfiles({"integration"})
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
Where:
#SpringBootTest is needed to avoid 'Failed to determine a suitable driver class exception'
args is needed to provide the required parameters to the batch
But still having this exception:
Error creating bean with name 'scopedTarget.createRequestForProposalWriter' defined in BatchConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.file.FlatFileItemWriter]: Factory method 'createRequestForProposalWriter' threw exception; nested exception is java.lang.NullPointerException: text
raised in the implementation of:
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForProposalWriter(
#Value("#{jobParameters['startDate']}") String startDate)
as the parameter 'startDate' is null.
In my naivety I assumed that I could test in isolation the classifier with something like that:
#Test
void shouldReturnProposalWriter() {
CsvData csvData = create-some-fake-data
CsvOutputClassifier csvOutputClassifier = new CsvOutputClassifier(
createRequestForProposalWriter,
createRequestForQuotationWriter);
ItemWriter itemWriter = csvOutputClassifier.classify(csvData);
some-assert-about-itemWriter-properties
}
So now the question is: how to correctly test the classifier?
You need to list DataSourceConfiguration as argument of #ContextConfiguration, i.e. your test class should start like this
#RunWith(SpringRunner.class)
#SpringBatchTest
#ContextConfiguration(classes = { BatchConfiguration.class, DataSourceConfiguration.class })
class CsvOutputClassifierTest {
...
}
The DataSourceConfiguration is currently not known within the test as you didn't declare it as part of the context or enabled classpath scanning in any form.
#DataJpaTest
class DataJpaVerificationTest {
#Autowired
private JdbcTemplate template;
#Test
public void testTemplate() {
assertThat(template).isNotNull();
}
}
When I run this test I get the following error:
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'kafkaConfig' defined in file
[***\config\KafkaConfig.class]:
Unsatisfied dependency expressed through constructor parameter 0;
nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type
'org.springframework.boot.autoconfigure.kafka.KafkaProperties'
available: expected at least 1 bean which qualifies as autowire
candidate. Dependency annotations: {}
where KafkaConfig class is a part of my application (app read data from Kafka and saves data to DB) and looks like:
#Configuration
#RequiredArgsConstructor
public class KafkaConfig {
private final KafkaProperties kafkaProperties;
#Bean
public Properties consumerProperties() {
Properties props = new Properties();
props.putAll(this.kafkaProperties.buildConsumerProperties());
return props;
}
}
Based on information that I googled about DataJpaTest annotation:
created application context will not contain the whole context needed
for our Spring Boot application, but instead only a “slice” of it
containing the components needed to initialize any JPA-related
components like our Spring Data repository.
So the question is: why Spring tries to load to the context Kafka specific bean for DataJpaTest?
I created a custom OAuth2AuthorizationRequestResolver that is initialized with a ClientRegistrationRepository and String authorizationRequestBaseUri. It is based on this implementation here https://www.baeldung.com/spring-security-custom-oauth-requests.
I initialize my custom resolver in my WebSecurityConfig.
#Autowired
private InMemoryClientRegistrationRepository inMemoryClientRegistrationRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
// ...
.oauth2Login(oAuth2LoginConfigurer -> oAuth2LoginConfigurer
.authorizationEndpoint()
.authorizationRequestResolver(new CustomtAuthorizationRequestResolver(inMemoryClientRegistrationRepository, "/oauth2/authorization"))
)
// ...
}
When I run the app, the InMemoryClientRegistrationRepository is correctly autowired and loads my clients from my application.yml.
In my test, I want to have the same InMemoryClientRegistrationRepository with the clients loaded from my application.yml. However, I cannot find a way to inject / autowire the InMemoryClientRegistrationRepository.
#SpringBootTest
#ContextConfiguration(classes = { TestConfiguration.class, InMemoryClientRegistrationRepository.class },
initializers = ConfigFileApplicationContextInitializer.class)
#ActiveProfiles("test")
public class CustomAuthorizationRequestResolverTests {
#Autowired
private InMemoryClientRegistrationRepository inMemoryClientRegistrationRepository;
#Test
public void firstTest() {
HttpServletRequest request = new MockHttpServletRequest();
OAuth2AuthorizationRequestResolver resolver = new CustomAuthorizationRequestResolver(inMemoryClientRegistrationRepository, "/oauth2/authorization");
resolver.resolve(request, "client");
// ...
}
}
With this code I get
Unsatisfied dependency expressed through field 'inMemoryClientRegistrationRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I can only find tests that use InMemoryClientRegistrationRepository by building it separately in the test and I cannot find any tests that load the clients from the application.yml. What do I have to do so that I can use my InMemoryClientRegistrationRepository with the values from my application.yml in my tests?
Update
I use the default TestConfiguration here and ConfigFileApplicationContextInitializer here classes from spring-boot-test-2.3.1.
I use Spring-Boot for my app, and I want to Autowired jobService of Spring Batch Admin to manage batch job myself.
But when i use this
#Autowied
Jobservice jobService;
It throws exception
No qualifying bean of type [org.springframework.batch.admin.service.JobService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
How can I fix this exception. Am I need to configure any thing about jobService.
I researched and try this
#Bean
public JobService jobService() throws Exception {
SimpleJobServiceFactoryBean factory = new SimpleJobServiceFactoryBean();
return factory.getObject();
}
But seem like not work and it throws another exception. Do I configure wrong in something?
SimpleJobServiceFactoryBean needs to be populated with other mandatory properties to create a Jobservice.
#Bean
public JobService jobService() throws Exception {
SimpleJobServiceFactoryBean factoryBean = new SimpleJobServiceFactoryBean();
factoryBean.setDataSource(new EmbeddedDatabaseBuilder().build());
factoryBean.setJobRepository((JobRepository) new MapJobRepositoryFactoryBean(
new ResourcelessTransactionManager()).getObject());
factoryBean.setJobLocator(new MapJobRegistry());
factoryBean.setJobLauncher(new SimpleJobLauncher());
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
I need to create a webservice client to get sportsdata.
But I'm getting an exception when trying to #Autowired sportsdata.
Exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [de.openligadb.schema.SportsdataSoap] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
JavaConfig:
#Configuration
#ComponentScan(basePackages = "com.example", excludeFilters = { #Filter(Configuration.class) })
public class MainConfig {
private #Value("${openligadb.wsdlDocumentUrl}") String wsdlDocumentUrl;
private #Value("${openligadb.endpointAddress}") String endpointAddress;
private #Value("${openligadb.namespaceUri}") String namespaceUri;
private #Value("${openligadb.serviceName}") String serviceName;
#Bean
public JaxWsPortProxyFactoryBean sportsdata() throws MalformedURLException {
JaxWsPortProxyFactoryBean ret = new JaxWsPortProxyFactoryBean();
ret.setWsdlDocumentUrl(new URL(wsdlDocumentUrl));
ret.setServiceInterface(SportsdataSoap.class);
ret.setEndpointAddress(endpointAddress);
ret.setNamespaceUri(namespaceUri);
ret.setServiceName(serviceName);
return ret;
}
#Bean
public static PropertySourcesPlaceholderConfigurer properties() {
PropertySourcesPlaceholderConfigurer ret = new PropertySourcesPlaceholderConfigurer();
ret.setLocation(new ClassPathResource("application.properties"));
return ret;
}
}
And yes: I know of #PropertySource but I need to create a bean for it to use it later in my Controller as well.
It's a FactoryBean interoperability problem with #Configuration. Take a look at this answer for details.
The short version is to add a bean explicitly to your configuration.
#Bean
public SportsdataSoap sportsdataSoap() throws ... {
return (SportsdataSoap) sportsdata().getObject();
}