I have a jar which will be included in spring boot application, I am trying to do an integration test in this, the project has the configuration class for creating the data source and JDBC template, I am using testing,
There is no application class in this project when this jar included in another project that project fetches data perfectly fine but not in same project
spring-boot-starter-test is added as a dependency
Configuration
#Configuration
public class DatabaseAccesManagementConfig {
#Bean(name = "accessmngmtDataSource")
#Qualifier("accessmngmtDataSource")
#ConfigurationProperties(prefix = "accessmngmt.datasource")
public DataSource accessmngmtDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "accessmngmtJdbcTemplate")
#Qualifier("accessmngmtJdbcTemplate")
public JdbcTemplate accessmngmtJdbcTemplate(#Qualifier("accessmngmtDataSource") DataSource accessmngmtDataSource) {
return new JdbcTemplate(accessmngmtDataSource);
}
}
Dao class
#Repository
public class ResourcePrivilegesDao {
static final Logger log = LoggerFactory.getLogger(ResourcePrivilegesDao.class);
#Autowired
#Qualifier("accessmngmtJdbcTemplate")
private JdbcTemplate jdbcTemplate;
public List<RP> getAll() {
log.debug("entering getAll()");
String sql = "SELECT * FROM rp";
RowMapper<RP> rowMapper = new RPRowMapper();
List<RP> result = this.jdbcTemplate.query(sql, rowMapper);
return result;
}
}
Test class
#SpringBootTest
#TestPropertySource(locations="classpath:application-test.properties")
#ContextConfiguration(classes = DatabaseAccesManagementConfig.class)
public class ResourcePrivilegesDaoTest {
#Autowired
DatabaseAccesManagementConfig databaseAccesManagement;
#Autowired
ResourcePrivilegesDao dao;
#Test
public void testGetAll() {
System.out.println(databaseAccesManagement);
List<ResourcePrivileges> list = dao.getAll();
Assert.notNull(list, "No resource privileges found");
Assert.notEmpty(list);
}
}
test property inside
environment=test
#Access management db details
accessmngmt.database.url=//xxyyy/am
accessmngmt.database.username=user
accessmngmt.database.password=password
In the test class, you missed the #RunWith(SpringRunner.class) which configure a unit test that need Spring's DI.
Take a look for the doc spring unit test
In order for the unit test to run a batch job, the framework must load the job's ApplicationContext. Two annotations are used to trigger this:
#RunWith(SpringJUnit4ClassRunner.class): Indicates that the class should use Spring's JUnit facilities
#ContextConfiguration(locations = {...}): Indicates which XML files contain the ApplicationContext.
Notice that, the SpringRunner is an alias for the SpringJUnit4ClassRunner. So we can use #RunWith(SpringRunner.class) instead of #RunWith(SpringJUnit4ClassRunner.class) with a shorter name.
Updated:
For the datasource properties injection, The #EnableConfigurationProperties annotation should annotated on the Test class.
Also, you use accessmngmt.datasource in DatabaseAccesManagementConfig class, while the prefix is not matched accessmngmt.database in application-test.properties. Here you must unify them, so you can inject the properties into the bean.
Related
I'm trying to test a class that has the #ConfigurationProperties annotation but without loading the entire Spring context. I tried using only the JUnit5's features in order to do that but until now didn't succeed in that.
I'm using spring-boot-starter-parent v2.6.2 .
The class I'm testing :
#ConfigurationProperties("db.mongo")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class MongoProperties {
private String host;
private String db;
private String user;
private String password;
}
The application.yaml :
db:
mongo:
host: localhost
db: test
user: test-user
password: secret
My Test class :
#ExtendWith(SpringExtension.class)
#EnableConfigurationProperties({MongoProperties.class})
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Test
public void mongoPropertiesLoadedTest(){
assertNotNull(properties.getDb());
assertNotNull(properties.getHost());
assertNotNull(properties.getPassword());
assertNotNull(properties.getUser());
}
}
The MongoProperties bean is injected successfully, but all the values inside are null and the asserts fail.
Adding the #SpringBootTest solves the issue of the null values in the instance of the bean, but it also starts the whole spring context which is what I don't want.
But using #ExtendWith(SpringExtension.class) will also start the spring context. The difference is that it starts the context in a traditional way but #SpringBootTest starts it in a spring-boot way. So no matter you use which of them , it still requires to start the spring context.
If your concern is to minimise the number of beans required to be loaded into the spring context when using #SpringBootTest, you can actually configure a specified #Configuration like the following as by default #SpringBootTest will load all beans defined in your applications which may be too much for testing (see this for details) :
#SpringBootTest
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
If you really want to just use #ExtendWith(SpringExtension.class) , you will lose the spring-boot feature such as externalising configuration features which cause you cannot load properties from application.properties and cannot support loading properties from YAML file etc. You have to manually configure ConfigDataApplicationContextInitializer to enable such features :
#ExtendWith(SpringExtension.class)
#ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
You can consider to further use #SpringJUnitConfig to combine #ExtendWith(SpringExtension.class) and #ContextConfiguration together which gives you :
#SpringJUnitConfig(initializers = ConfigDataApplicationContextInitializer.class)
public class MongoPropertiesTest {
#Autowired
private MongoProperties properties;
#Configuration
#EnableConfigurationProperties({MongoProperties.class})
public static class Config {
}
}
Actually both approaches do not have much differences in term of speed , so I prefer to just use #SpringBootTest for simplicity as it does not requires you to configure ConfigDataApplicationContextInitializer.
I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.
I am trring to read a value from the properties file in my junit setup using spring boot.
I can not read the value. Below is my content:-
application-test.properties
my.user.name=Amar
COnfig file to create beans:
#Configuration
#ActiveProfiles("test")
#Profile("test")
public class RdbmsTestConfig {
#Value("${my.user.name}")
private String name;
#Bean
public String myString(){
return "Amar";
}
#Bean
public PropsHolder propsHolder() {
PropsHolder propsHolder = new PropsHolder();
propsHolder.setUserName(name);
return propsHolder;
}
}
My test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RdbmsTestConfig.class)
#ActiveProfiles("test")
public class TestRoomService {
#Autowired
#Qualifier("myString")
private String myString;
#Autowired
private PropsHolder propsHolder;
#Autowired
private Environment env;
#Test
public void userTest() {
Arrays.stream(env.getActiveProfiles()).forEach(System.out::println);
System.out.println(propsHolder.getUserName());
Assert.assertNotNull(myString);
Assert.assertEquals("Amar",myString);
}
}
The value for propsHolder.getUserName comes out to be ${my.user.name}
First remove #ActiveProfiles("test") from your class RdbmsTestConfig. Then your test just defines the RdbmsTestConfig as spring context. As I can see you do not run a real spring boot test. The problem is you do not have any PropertyPlaceholderConfigurer configured in your spring config. So either configure one PropertyPlaceholderConfigurer or add #SpringBootTest to your test if you have any SpringBootApplication.
I've never used #Profile(), so i'm not sure if that is supposed to do what you want it to do. But I'm always using #PropertySources(), because otherwise, how is the code supposed to know where to look for the properties?
#Configuration
#PropertySources(value = { #PropertySource("classpath:core-
test.properties") })
I created a base test class that has the required annotations:-
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RdbmsTestConfig.class)
#ActiveProfiles("test")
#SpringBootTest
public abstract class BastTest { }
#ActiveProfiles set the profile to use used, I dont have to mention it in the application.properties file
My test class now extends this:-
public class TestRoomService extends BastTest
In my RdbmsTestConfig remove #ActiveProfiles annotation.
My classes are..
lies in src/intregation-test/java
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplication.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
lies in src/main/java
#SpringBootApplication
public class StoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Now the problem is I wanted to exclude a class
from being get component scanned.my this class contains the dependency for apache Kafka.
if this class loads while container start up it start looking for kafka running instances.
so while running Intregation test I will not be starting my Kafka server,so I wanted to run
Intregation test making kafka shutdown.
This I can achieved by adding one line code in StoreOrderFulfillmentApplication class
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
by addding this line of code StoreOrderFulfillmentApplication class it is excluding OrderReceiveEventConfiguration class from being get component scanned.
now the problem is I not suppose add any test configuration changes in the main code.
so I am struggling to do the same exclusion from src/intregation-test/java source folder, is their some way that I can exclude this particular class during container startup code.
but it should not affect my main class code means code inside src/main/java
Any help is Appreciated..
You can make use of #Conditional as shown below.
In application.properties introduce a property say kafka.enabled.
Annotate the OrderReceiveEventConfiguration with #Conditional(PropertyCondition.class)
Depending on kafka.enabled value viz. true (for normal run) or false (for testing) the OrderReceiveEventConfiguration will be picked up or ignored respectively without changing the code.
Let know in comments in case any more information is required.
Except main #conditional annotation there are set of similar annotation to be used for different cases.
Class conditions
The #ConditionalOnClass and #ConditionalOnMissingClass annotations allows configuration to be included based on the presence or absence of specific classes.
E.g. when OObjectDatabaseTx.class is added to dependencies and there is no OrientWebConfigurer bean we create the configurer.
#Bean
#ConditionalOnWebApplication
#ConditionalOnClass(OObjectDatabaseTx.class)
#ConditionalOnMissingBean(OrientWebConfigurer.class)
public OrientWebConfigurer orientWebConfigurer() {
return new OrientWebConfigurer();
}
Bean conditions
The #ConditionalOnBean and #ConditionalOnMissingBean annotations allow a bean to be included based on the presence or absence of specific beans. You can use the value attribute to specify beans by type, or name to specify beans by name. The search attribute allows you to limit the ApplicationContext hierarchy that should be considered when searching for beans.
See the example above when we check whether there is no defined bean.
Property conditions
The #ConditionalOnProperty annotation allows configuration to be included based on a Spring Environment property. Use the prefix and name attributes to specify the property that should be checked. By default any property that exists and is not equal to false will be matched. You can also create more advanced checks using the havingValue and matchIfMissing attributes.
#ConditionalOnProperty(value='somebean.enabled', matchIfMissing = true, havingValue="yes")
#Bean
public SomeBean someBean(){
}
Resource conditions
The #ConditionalOnResource annotation allows configuration to be included only when a specific resource is present.
#ConditionalOnResource(resources = "classpath:init-db.sql")
Web application conditions
The #ConditionalOnWebApplication and #ConditionalOnNotWebApplication annotations allow configuration to be included depending on whether the application is a 'web application'.
#Configuration
#ConditionalOnWebApplication
public class MyWebMvcAutoConfiguration {...}
SpEL expression conditions
The #ConditionalOnExpression annotation allows configuration to be included based on the result of a SpEL expression.
#ConditionalOnExpression("${rest.security.enabled}==false")
You should able able to create a separate config class in your test package
#SpringBootApplication
#ActiveProfiles("Test")
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class StoreOrderFulfillmentApplicationTest {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplicationTest.class, args);
}
}
And then in your test class
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = StoreOrderFulfillmentApplicationTest.class)
#ActiveProfiles("Test")
public class OrderCreationIntregationTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private OrderRepository orderRepository;
#MockBean
private OrderLineItemRepository orderLineItemRepository;
#MockBean
private InternalEventPublisher internalEventPublisher;
#SuppressWarnings("unchecked")
#Before
public void setup() {
Mockito.when(orderRepository.findByOfsReferenceId("OFS:GMO:Z100002062-99")).thenReturn(null);
OrderEntity savedOrder = new OrderEntity();
savedOrder.setOrderId(1023);
Mockito.when(orderRepository.save(Mockito.any(OrderEntity.class))).thenReturn(savedOrder);
Iterable<OrderLineItemEntity> orderLineItemList = prepareOrderLineItemEntityIterable();
Mockito.when(orderLineItemRepository.save(Mockito.any(Iterable.class))).thenReturn(orderLineItemList);
}
#Test
public void test() throws ParseException {
FulfillmentOrder fulfillmentOrderRequestVO = new FulfillmentOrder();
fulfillmentOrderRequestVO = buildFulfillmentOrder();
String myMessage = "Order Created";
ResponseEntity<ResponseOrderMessage> responseEntity = restTemplate.postForEntity("/fulfillmentprocessor/orders",
fulfillmentOrderRequestVO, ResponseOrderMessage.class);
ResponseOrderMessage responseOrderMessage = responseEntity.getBody();
assertEquals(HttpStatus.CREATED, responseEntity.getStatusCode());
assertEquals(myMessage, responseOrderMessage.getMessage());
}
Create a test application class
#SpringBootApplication
#ComponentScan(basePackages = "com.tesco.store.order.fulfillment.processor", excludeFilters = #Filter(type = FilterType.ASSIGNABLE_TYPE, classes = OrderReceiveEventConfiguration.class))
public class TestStoreOrderFulfillmentApplication {
public static void main(String[] args) {
SpringApplication.run(StoreOrderFulfillmentApplication.class, args);
}
}
Add the following configuration annotation to you test class
#SpringApplicationConfiguration(classes = TestStoreOrderFulfillmentApplication .class)
Good day. My Spring Boot app uses Postgress database. For tests it uses H2 database. When running in non-test mode beans need to be initialized in this order:
1) Init DataSource
2) Init JPA beans
When running in test mode I need to create and populate H2 database before JPA beans initialization:
1) Init DataSource
2) Init DataSourceInitializer
3) Init JPA beans
The problem is that JPA beans get initialized before DataSourceInitializer (step 3 precedes step 2) and test fails on missing tables (hibernate.hbm2ddl.auto=validate).
Step 1
#Configuration
#EnableTransactionManagement
public class DataSourceConfig {
#Primary
#Bean
#ConfigurationProperties(prefix = "datasource.runtime")
public DataSource runtimeDataSource() {
return DataSourceBuilder.create().build();
}
}
Step 2
#Configuration
#Profile(Profiles.INTEGRATION_TEST)
public class DataSourceTestConfig {
#Autowired
private ResourceLoader resourceLoader;
#Bean
public DataSourceInitializer runtimeDataSourceInitializer(#Qualifier("runtimeDataSource") DataSource dataSource) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(new ResourceDatabasePopulator(
resourceLoader.getResource("classpath:runtime/schema.sql")
));
return initializer;
}
}
Step 3
#Configuration
#EnableTransactionManagement
public class JpaConfig {
#Autowired
private Environment environment;
#Autowired
#Qualifier(value = "runtimeDataSource")
private DataSource runtimeDataSource;
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(runtimeDataSource)
.properties(hibernateSettings())
.packages(
"cz.adx.anx.car.cases.domain",
"cz.adx.anx.car.lib.domain",
"org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
)
.persistenceUnit("runtimePersistenceUnit")
.build();
}
}
I need beans from class DataSourceTestConfig get initialized before JpaConfig and after DataSourceConfig but only in test mode. In non-test mode beans from JpaConfig should be initialized after DataSourceConfig and beans from DataSourceTestConfig must be omited. Therefore I cannot annotate class JpaConfig with #DependsOn beans from class DataSourceTestConfig because this class is located in test packages and not present in non-test mode. I could duplicate config classes and make them conditional on profile but I don't feel comfortable with this solution. Please, is there a better solution? Thanks in advance!
PS: My app uses two databases/datasources but I shortened the code above to make it easier to read. I'm using Spring Boot 1.3.1.RELEASE.
UPDATE 1:
I tried to use approach suggested by #luboskrnac. I placed annotation ActiveProfiles on my integration test classes:
#ActiveProfiles("IT")
public abstract class IntegrationTest {...}
And I used annotation Profile on relevant beans in class JpaConfig shown below:
#Configuration
#EnableTransactionManagement
public class JpaConfig {
#Autowired
private Environment environment;
#Autowired
#Qualifier(value = "runtimeDataSource")
private DataSource runtimeDataSource;
#Autowired
#Qualifier(value = "configDataSource")
private DataSource configDataSource;
#Profile("!IT")
#Bean(name = "runtimeEntityManagerFactory")
#DependsOn("runtimeDataSource")
public LocalContainerEntityManagerFactoryBean runtimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return createRuntimeEntityManagerFactory(builder);
}
#Profile("IT")
#Bean(name = "runtimeEntityManagerFactory")
#DependsOn("runtimeDataSourceInitializer")
public LocalContainerEntityManagerFactoryBean testRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return jpaConfig.createRuntimeEntityManagerFactory(builder);
}
public LocalContainerEntityManagerFactoryBean createRuntimeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(runtimeDataSource)
.properties(hibernateSettings())
.packages(
"cz.adx.anx.car.cases.domain",
"cz.adx.anx.car.lib.domain",
"org.springframework.data.jpa.convert.threeten" // Hibernate support for Java 8 date and time classes
)
.persistenceUnit("runtimePersistenceUnit")
.build();
}
}
And I'm creating the transaction managers the same way. Because I use two datasources (two different databases) I use bean names in the EnableJpaRepositories annotation.
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "runtimeEntityManagerFactory",
transactionManagerRef = "runtimeTransactionManager",
basePackages = "cz.adx.anx.car.lib.repository"
)
public class JpaCarLibRepositoryConfig {
}
So I need the non-test bean and test bean registered under the same name. But Spring gives me an exception:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: runtimeEntityManagerFactory
Any advices please?
I would suggest to drop any considerations about explicit bean creation ordering or bean dependencies.
Simply populate database in test based on Spring #Sql annotation. Test may look something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#Sql("/test-schema.sql")
public class DatabaseTests {
#Test
public void emptySchemaTest {
// execute code that uses the test schema without any test data
}
#Test
#Sql({"/test-schema.sql", "/test-user-data.sql"})
public void userTest {
// execute code that uses the test schema and test data
}
}
If you'll need to swap datasource (e.g. using PostgereSQL in PROD and H2 in tests), just use Spring #Profile, #ActiveProfiles annotations.