I'm using #TestConfiguration annotation to define bean provider for the JUnit5 test class.
A test class is annotated with:
#Import(MyTestConfiguration.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT).
package com.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
#Import(MyTestConfiguration.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class MyTest {
...
}
A configuration class is annotated with
#TestConfiguration.
package com.example;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
#TestConfiguration
public class MyTestConfiguration {
#LocalServerPort
private int port;
...
}
I'm trying to inject local server port inside the configuration class with #LocalServerPort annotation, but get a Failed to load ApplicationContext error Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.example.MyTestConfiguration': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'local.server.port' in value "${local.server.port}".
I was using #LocalServerPort successfully in the test class, but decided to move it along with other objects which use it to the MyTestConfiguration class to make test class more clear.
Add #Lazy annotation to the test configuration class. More info here.
From the documentation:
If this annotation is not present on a #Component or #Bean definition,
eager initialization will occur. If present and set to true, the #Bean
or #Component will not be initialized until referenced by another bean
or explicitly retrieved from the enclosing BeanFactory. If present and
set to false, the bean will be instantiated on startup by bean
factories that perform eager initialization of singletons.
Related
Here is my DatasourceConfiguration.java file. It's a configuration file, to set up the Hikari config and the QuartzDatasource is built based on the Hikari config.
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class DatasourceConfiguration {
#Value("${spring.datasource.scheduler.jdbcUrl}")
private String jdbcUrl;
#Value("${spring.datasource.scheduler.username}")
private String userName;
#Value("${spring.datasource.scheduler.driverClassname}")
private String driverClassname;
#Value("${spring.datasource.scheduler.password}")
private String password;
#Bean
public HikariConfig hikariConfig() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(driverClassname);
hikariConfig.setJdbcUrl(jdbcUrl);
hikariConfig.setUsername(userName);
hikariConfig.setPassword(password);
return hikariConfig;
}
#Bean(name = "quartzDataSource")
#QuartzDataSource
public DataSource dataSource() {
return new HikariDataSource(hikariConfig());
}
}
And here is my DatasourceConfigurationTest.java file:
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.ApplicationContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.sql.DataSource;
import java.sql.SQLException;
import static org.assertj.core.api.Assertions.assertThat;
#ExtendWith(SpringExtension.class)
#TestPropertySource(locations = "classpath:application.properties")
#ContextConfiguration(classes = DatasourceConfiguration.class)
class DatasourceConfigurationTest {
#Autowired
private ApplicationContext context;
#Test
void validateConfiguration() throws SQLException {
assertThat(this.context.getBeansOfType(DataSource.class)).hasSize(1);
HikariDataSource dataSource = this.context.getBean(HikariDataSource.class);
assertThat(dataSource.getConnection().getMetaData().getURL()).isEqualTo("jdbc:h2:mem:simple");
assertThat(dataSource.getMaximumPoolSize()).isEqualTo(42);
}
}
When I run the Unit Test, I recognized that it failed to load ApplicationContext.
Here is the log:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'getHikariConfig' defined in com.gm.gcc.vas.utility.messageretry.config.DatasourceConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zaxxer.hikari.HikariConfig]: Factory method 'getHikariConfig' threw exception; nested exception is java.lang.RuntimeException: Failed to load driver class test-driver-classname in either of HikariConfig class loader or Thread context classloader
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.zaxxer.hikari.HikariConfig]: Factory method 'getHikariConfig' threw exception; nested exception is java.lang.RuntimeException: Failed to load driver class test-driver-classname in either of HikariConfig class loader or Thread context classloader
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
... 87 common frames omitted
Caused by: java.lang.RuntimeException: Failed to load driver class test-driver-classname in either of HikariConfig class loader or Thread context classloader
at com.zaxxer.hikari.HikariConfig.setDriverClassName(HikariConfig.java:491)
at com.gm.gcc.vas.utility.messageretry.config.DatasourceConfiguration.getHikariConfig(DatasourceConfiguration.java:37)
... 88 common frames omitted
Here is the properties file:
spring.datasource.scheduler.driverClassname=test-driver-classname
spring.datasource.scheduler.jdbcUrl=jdbc:test-url
spring.datasource.scheduler.password=test-password
spring.datasource.scheduler.username=test-username
As my understanding, when trying to create a connection, it failed because of dummy values.
I just wonder if my approach to do the unit test is correct or not, if yes, how can I fix the ApplicationContext error? Else, what is the correct way to ensure 100% converage.
If you want to execute integration tests, you need a running DB instance to which Spring can connect. Your test is not a unit test as it is extended with the SpringExtension.
You could use an embedded H2 database for your tests, you would then need to add H2 as test dependency in your pom.xml:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
In your application-test.properties:
spring.datasource.scheduler.driverClassname=org.h2.Driver
spring.datasource.scheduler.jdbcUrl=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
spring.datasource.scheduler.password=sa
spring.datasource.scheduler.username=sa
In order to have the application-test.properties loaded for your test you should either replace the #TestPropertySource(locations = "classpath:application.properties") with #ActiveProfiles("test") or reference the file #TestPropertySource(locations = "classpath:application-test.properties").
Another good alternative would be using the Testcontainers project which allows you to run the database that you use in production (like Postgres or MySQL) in a docker container. Here is a good tutorial by Baeldung.
I have a class named MyConfig.java.
import javax.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MyConfig {
#NotNull
#Value("${test.key}")
public String queryIndex;
}
My question is, how is it getting validated on the StartUp of Spring Boot application without any #Validated or #Valid. I Understand #Configuration classes are picked up by Spring container on Startup (to generate bean definitions if any) and javax.validator is on classpath.
The below code snippet makes sense because added #Valid annotation on POJO class :
#PostMapping
public String test(#Valid #RequestBody Employee emp) {
return "";
}
From Spring Doc:-
Link Here for reference
3.8.1. Overview of the JSR-303 Bean Validation API
JSR-303 standardizes validation constraint declaration and metadata for the Java platform. Using this API, you annotate domain model properties with declarative validation constraints and the runtime enforces them.
Here is the solution:-
MyConfig.java
import javax.validation.constraints.NotBlank;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Configuration
#Validated
#ConfigurationProperties(prefix="test")
public class MyConfig {
private Logger logger = LoggerFactory.getLogger(MyConfig.class);
private static final String MYNAME = MyConfig.class.getSimpleName();
#NotBlank(message = "Key should not be empty")
#Value("${test.key}")
public String test;
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
application.properties
server.port=5555
test.key=
test.name=
This dependency is a must in pom.xml
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
So, #Validated, #ConfigurationProperties combined with javax.validation constraints are working behind the scenes.
Error Description:-
Binding to target org.springframework.boot.context.properties.bind.BindException:
Failed to bind properties under 'test' to com.cgi.demo.MyConfig$$EnhancerBySpringCGLIB$$3dcd81ec failed:
Property: test.test
Value:
Reason: Key should not be empty
For your reference from SprinBoot Doc's:-
Click Here for more info
2.7.9. #ConfigurationProperties Validation
Spring Boot attempts to validate #ConfigurationProperties classes whenever they are annotated with Spring’s #Validated annotation. You can use JSR-303 javax.validation constraint annotations directly on your configuration class. To do so, ensure that a compliant JSR-303 implementation is on your classpath and then add constraint annotations to your fields.
I´m trying to configurate new properties for the test, so I created a test config class:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.scheduling.annotation.EnableAsync;
#Configuration
#EnableAsync
#ComponentScan("ar.com.yo")
#PropertySource("test.properties")
public class TestConfig {
}
Properties file is in src/test/resources/test.properties
and in the test class :
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration(classes = TestConfig.class)
public class InsumoServiceTest {
...
}
when I execute the tests the error is:
Failed to parse configuration class [ar.com.yo.myproject.main.TestConfig]; nested exception is java.io.FileNotFoundException: Could not open ServletContext resource [/test.properties]
It seems that the requested properties can't be found. I would recommend doing this:
is that resolve your problem :
#PropertySource("classpath:test.properties")
I have a multimodule springboot project. Here is the architecture:
The class annotated with #SpringBootApplication is in the top module (webservice). When I run integration test from this top module using #SpringBootTestin my test classes, it works fine.
But now I'd like to run integration test from the business module. #SpringBootTest only doesn't work anymore because no config class can be found in the business module. So I created a config class in the business module:
package com.berthoud.p7.webserviceapp.business.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.ComponentScan;
#SpringBootConfiguration
#ComponentScan(basePackages = "com.berthoud.p7")
public class TestContextConfiguration {
}
And in my test classes, I pointed to this config class, like this :
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestContextConfiguration.class)
public class BookResearchIT {
//my tests...
}
Doing so, I was hopping that Spring would add to the context all the beans declared in the package com.berthoud.p7 and its subfolders. Indeed, when I autowire spring beans in my test classes it now looks fine (IntelliJ doesnt tell anymore that the #autowiredbeans cannot be autowired):
But nevertheless, when I run my tests, Spring fails to load the application context :
***************************
APPLICATION FAILED TO START
***************************
Description:
Field bookReferenceDAO in com.berthoud.p7.webserviceapp.business.BookResearchManager required a bean of type 'com.berthoud.p7.webserviceapp.consumer.contract.BookReferenceDAO' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.berthoud.p7.webserviceapp.consumer.contract.BookReferenceDAO' in your configuration.
I don't understand this. Here is how I declared the beans concerned :
package com.berthoud.p7.webserviceapp.business;
#Service
public class BookResearchManager {
#Autowired
BookReferenceDAO bookReferenceDAO;
#Autowired
LibrairyDAO librairyDAO;
#Autowired
BookDAO bookDAO;
package com.berthoud.p7.webserviceapp.consumer.contract;
public interface BookReferenceDAO {
// method signatures
}
package com.berthoud.p7.webserviceapp.consumer.repositories.SpringDataJPA;
public interface BookReferenceRepository extends CrudRepository<BookReference, Integer>, BookReferenceDAO {
What did I do wrong?
EDIT: after changing my config class like this :
package com.berthoud.p7.webserviceapp.business.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#SpringBootConfiguration
#ComponentScan(basePackages = "com.berthoud.p7.webserviceapp")
#EnableJpaRepositories(basePackages = "com.berthoud.p7.webserviceapp.consumer")
#EntityScan(basePackages = "com.berthoud.p7.webserviceapp")
public class TestContextConfiguration {
}
I now have a different error :
Field bookReferenceDAO in com.berthoud.p7.webserviceapp.business.BookResearchManager required a bean named 'entityManagerFactory' that could not be found.
I am having an issue in which a defined repository is not being correctly interpreted as a bean on server startup. The class with #SpringBootApplication is in a higher directory than the defined repository, so I cannot find why it does not configure.
#SpringBootApplication:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.Properties;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
System.out.println("http://localhost:8080");
}
}
Repository
package lab14.panoslab.Repositories;
import lab14.panoslab.Models.Account;
import org.apache.catalina.User;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
#Repository
public interface UserRepository extends JpaRepository<Account,Long> {
#NotFound(action = NotFoundAction.IGNORE)
List<User> findByUsername(String username);
}
Error code:
*************************** APPLICATION FAILED TO START***************************
Description:
Field userRepository in lab14.panoslab.Controllers.RegisterController
required a bean of type 'lab14.panoslab.Repositories.UserRepository'
that could not be found.
Action:
Consider defining a bean of type
'lab14.panoslab.Repositories.UserRepository' in your configuration.
Process finished with exit code 1
Are you sure that your class Account implements the interface User?
And try to remove the annotation #Repository and add annotations #EntityScan and #EnableJpaRepositories in your DemoApplication class:
#SpringBootApplication
#EntityScan({"lab14.panoslab.Models"})
#EnableJpaRepositories({"lab14.panoslab.Repositories"})
public class DemoApplication {...}
Also, I would advise you to rename all your packages into lowercase and return a value of List<Account>, not List<User>.
I did face similar issue. What I have done the mistake is, I have placed my controller/repository and other component packages outside the Main Class package. So, Spring boot not able to identify my components,
For Ex: main class package is package com.example.demo;
Controller package like, package com.example.controller;
Repository package like, package com.example.repository;
Below are the two different ways to solve this problem,
Explicitly defining my component packages in #ComponentScan, like #ComponentScan(basePackages="com.example.controller,com.example.repository") with base packages of required components.
Otherwise, You can create Controller/repository packages inside the main package. So, you no need to define #ComponentScan and all.
For ex,
main class package is package com.example.demo;
Controller package like, package com.example.demo.controller;
Repository package like, package com.example.demo.repository;