Mock the Spring Environment Object In Junit with #ContextConfiguration - java

I have got a test like this
RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { AppConfig.class, TestConfig.class})
public class MyTest {
........
}
The AppConfig is the main config for my app, the TestConfig is the test config
which loads the test properties
#Configuration
#PropertySource("classpath:test_dev.properties")
public class DevConfig {
#Bean
public DataSource getDataDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("driverclass"));
dataSource.setUrl(env.getProperty("url"));
dataSource.setUsername(env.getProperty("username"));
dataSource.setPassword(env.getProperty("password"));
return dataSource;
}
}
problem is The test_dev.properties file has a encrypted password field
driverclass = ojdbc:xxx
url = xxxxxx
username = abc
password = #'"#~£$%
I need to use decryptor to decrypt it, then the decrypted password on the env object. thus env.get("password"), the real password will return
so my question is how can I mock the Environment object before the DataSource object gets crated.

in my case, I wanted to mock the password filed on the datasource object
I first let the datasource bean initialize, then in my integration test autowired the mybatis Environment (don't be confused with springs environment).
from the env you can get the datasource.
from the #beforetest section, the trick is to use Reflectiontestutils.setfield to set the password field on the datasource
alternatively you could inject the entire datasource on the target environment
but I didn't try
the key motivation for using Spring is to easily inject mock objects.

Related

How do I unit-test the controller of a Spring Boot application with a DataSource dependency in a #ComponentScan #Configuration

Consider the following basic Spring Boot application:
#SpringBootApplication
#ComponentScan(basePackages = "webmvctestproblem.foo")
public class Application {
public static void main(String[] args) {
run(Application.class, args);
}
}
It contains only two other beans. One controller:
#RestController
class Greeter {
#GetMapping("/")
String greet() {
return "Hello, world!";
}
}
And one configuration in webmvctestproblem.foo containing a DataSource dependency:
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
}
Running the application normally (through gradlew bootrun, e.g.) succeeds. Thus, confirming that the app is configured correctly under normal conditions.
However, running the following test causes a runtime error because Spring still attempts to resolve the data source bean dependency on the configuration class:
#RunWith(SpringRunner.class)
#WebMvcTest
public class GreeterTest {
#Test
public void test() throws Exception {
}
}
Of course, there isn't one to resolve because the test is a #WebMvcTest that is designed to create only MVC-related beans.
How do I get rid of the error? I have already tried excluding the configuration class using the excludeFilters attribute of the existing #WebMvcTest annotation and a new #ComponentScan annotation on the test itself. I don't want to resort to turning it into an integration test with #SpringBootTest.
(The project is also available on GitHub for convenience.)
If the DataSource is not mandatory for the test run, simply mock the DataSource with #MockBean in the test class.
#RunWith(SpringRunner.class)
#WebMvcTest
public class GreeterTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private DataSource dataSource;
#Test
public void shouldGreet() throws Exception {
mockMvc
.perform(get("/"))
.andExpect(content().string("Hello, world!"));
}
}
Spring will automatically create a Mock for DataSource and inject it into the running test application context.
Based on your source code it works.
(Btw: Your source code has a minor issue. The Greeter controller class is in the base package but the component scan only scans on the "foo" package. So there will be no Greeter controller on the test run if this isn't fixed.)
#WebMvcTest creates a "slice" of all the beans relevant to WebMvc Testing (Controllers, Json conversion related stuff and so forth).
You can examine the defaults in org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTypeExcludeFilter
In order to find which beans are actually supposed to be Run Spring must resolve them somehow, right?
So spring test tries to understand what should be loaded and what not by passing through these filters.
Now, if you mark anything with #Configuration spring "knows" that this is the place where the place should be found. So it will load the configuration and then will check which beans defined in this configuration must actually be loaded. However the object of configuration itself must be loaded anyway.
Now the process of loading the configuration object includes injecting stuff into these configurations - this is lifecycle of object creation of spring.
And this is a source of mistake here:
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
}
Spring loads Bar and tries as a part of loading this object to autowire the data source. This fails since the DataSource Bean itself is excluded by filters.
Now in terms of solution:
First of all, why do you need this DataSource to be autowired in the Configuration object? Probably you have the bean that uses it, lets call it "MyDao", otherwise I don't see a point of such a construction, since #Configuration-s are basically a place to define bean and you shouldn't put business logic there (if you do - ask a separate question and me/our colleagues will try to help and suggest better implementation).
So I assume you have something like this:
public class MyDao {
private final DataSource dataSource;
public MyDao(DataSource dataSource) {
this.dataSource = dataSource;
}
}
#Configuration
class Bar {
#Autowired
private DataSource dataSource;
#Bean
public MyDao myDao() {
return new MyDao(dataSource);
}
}
In this case however you can rewrite the configuration in a different way:
#Configuration
class Bar {
// Note, that now there is no autowired datasource and I inject the parameter in the bean instead - so that the DataSource will be required only if Spring will have to create that MyDao bean (which it won't obviously)
#Bean
public MyDao myDao(DataSource dataSource) {
return new MyDao(dataSource);
}
}
Now the Bar object will still be created - as I've explained above, but it beans including MyDao of course won't be created, problem solved!
The solution with #Autowired(required=false) provided by #Anish B. should also work - spring will attempt to autowire but won't fail because the data source is unavailable, however you should think whether its an appropriate way to deal with this issue, your decision...
Before you can #Autowire the DataSource bean you need to define the DataSource in some config class or in the properties file. Something like this
spring.datasource.url = jdbc:mysql://localhost/abc
spring.datasource.name=testme
spring.datasource.username=xxxx
spring.datasource.password=xxxx
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
Or
#Configuration
public class JpaConfig {
#Bean
public DataSource getDataSource()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:file:C:/temp/test");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
return dataSourceBuilder.build();
}
You should use
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#RunWith(SpringRunner.class)
on your test class, then you can inject
#Autowired
private MockMvc mockMvc;
and use it in test
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andReturn();
i suggest you read the documentation about testing. You can test a spring boot application in 100s of different ways.
WebMvcTest
as suggested by the documentation try, defining what controller class that you want to test in the #WebMvcTest annotation.
#RunWith(SpringRunner.class)
#WebMvcTest(Greeter.class)
public class GreeterTest {
#Autowired
private MockMvc mockMvc;
#Test
public void shouldGreet() throws Exception {
mockMvc
.perform(get("/"))
.andExpect(content().string("Hello, world!"));
}
}

Cannot inject default MySQL datasource

I am trying hard to inject the default datasource but I have the following error:
javax.enterprise.inject.IllegalProductException: Normal scoped producer method may not return null:
io.quarkus.agroal.runtime.DataSourceProducer.createDefaultDataSource()
This is my current situation:
application.properties
quarkus.datasource.driver = com.mysql.cj.jdbc.Driver
quarkus.datasource.jdbc.url=jdbc:mysql://localhost:3307/sandbox
quarkus.datasource.username=root
quarkus.datasource.password=password
quarkus.datasource.jdbc.min-size=0
quarkus.datasource.jdbc.max-size=11
MyClass.java
#ApplicationScoped
public class MyClass {
#Inject
AgroalDataSource dataSource;
void methodUsingDataSource() {...}
}
However I made it works with a named datasource with the SAME configuration as the default one:
application.properties
quarkus.datasource.users.driver = com.mysql.cj.jdbc.Driver
quarkus.datasource.users.url=jdbc:mysql://localhost:3307/sandbox
quarkus.datasource.users.username=root
quarkus.datasource.users.password=password
quarkus.datasource.users.min-size=0
quarkus.datasource.users.max-size=11
MyClass.java
#ApplicationScoped
public class MyClass {
#Inject
#DataSource("users")
AgroalDataSource dataSource;
void methodUsingDataSource() {...}
}
Do you have any idea about how to fix this behaviour? This causes me issues when I want to setup Hibernate.
Remove this line:
quarkus.datasource.driver = com.mysql.cj.jdbc.Driver
and use this instead:
quarkus.datasource.db-kind = mysql
You're mixing deprecated and new configuration properties in the default datasource and your combination of it doesn't work.
The example you gave with the named datasource is only using the old configuration properties so it works OK.
Now I need to understand why you didn't have a proper error message. I'll go work on improving that.

LocalContainerEntityManagerFactoryBean

I have gone through the spring data jpa reference documentation
to configure a datasource in spring boot,and with LocalcontainerEntityManagerFactoryBean and transactionManager..etc,but run it
with error
but I want configure a datasource of mysql,single datasource.
this is configuration class code:
#Configuration
#EnableJpaRepositories
#EnableTransactionManagement
public class DataSourceConfig {
#Bean
#ConfigurationProperties(prefix="oneslide.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendor=new HibernateJpaVendorAdapter();
vendor.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory=new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendor);
factory.setPackagesToScan("com.oneslide.multiDataSource.domain");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager=new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
I don't want user DatasourceBuilder.create.url().password().. something chain invocation like that,I just want to congiure my sql connection metadata in
application.properties with oneslide.datasource namespce.And try to use the LocalContainerEntityManagerFactory Bean,not with tutorial's way in which they
use spring.datasource.* property.
but when i run it datasource debug info is null,there it is digest of exception log:
Invocation of init method failed; nested exception is
org.hibernate.service.spi.ServiceException: Unable to create requested
service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
: Access to DialectResolutionInfo cannot be null when
'hibernate.dialect' not set
Help me thanks a lot.
I totally misunderstand spring boot,Maybe.....it autoconfigure all bean,like
LocalContainerEntityManagerfactoryBean!so to use multidatasource,I just need to configure a datasource only,Right???
Your annotation #ConfigurationProperties(prefix="oneslide.datasource") is asking Sprint to get the info from the external configuration and bind it with the bean you are annotating, i.e. the result produced by the method.
BUT the annotated bean MUST have the properties to receive the configuration values. I.e. it has to have fields and inner objects that replicate the structure of the configurations you are passing (and the setters too).
As example if your config contains something like below:
oneslide.datasource.url = some_url
oneslide.datasource.user = usr
oneslide.datasource.password = pw
oneslide.datasource.special.detail = whatever
The bean you build should have fields "url", "user" and "password" AND an object "special" with a field "detail", so that Spring can set the values. Simplifying something along Y = X.getSpecial(); Y.setDetail() (with null recognition and object creation too, I think to remember).
If you do nothing... Spring behind the scene will create a DataSourceProperties bean (that unsurprisingly contains the fields normally used to set up a datasource with the config info under "spring.datasource").
You can get hold of this bean by defining your own bean that gets it as a parameter, like below:
public <whatever> getTheD_S_Properties(DataSourceProperties myDataSourceValesFromConfig) {
...do something with the bean you got,
that contains the values from your config...
}
The most common operation, in this case is to just build the datasource yourself, with some logic beyond just assigning the values form the config.
If you need to do nothing special, then let Spring to build the datasource too.
Just sit back and enjoy ! :)

Configuring the database properties from outside of intellij

I want to ask as currently I have my database properties like username and password inside the persistence layer in the intellij. But I want to place it somewhere outside so if someone wants to change the password or any configuration inside database he should not have to dig inside my current structure. Now my structure is persistence then main then resources and then dbconfig properties so is there any way I can do it.
You can create a file app.properties in your resources folder with all database information you need:
# Datasource details
testapp.db.driver = org.h2.Driver
testapp.db.url = jdbc:h2:mem:test
testapp.db.username = username
testapp.db.password = password
Then you can refer to it in your Java code as:
#Configuration
#PropertySource("app.properties")
public class DataConfig {
#Autowired
private Environment env;
#Bean
public DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName(env.getProperty("testapp.db.driver"));
ds.setUrl(env.getProperty("testapp.db.url"));
ds.setUsername(env.getProperty("testapp.db.username"));
ds.setPassword(env.getProperty("testapp.db.password"));
return ds;
}
}

Spring Boot not reading from property file

I have tried every option on web but not able to set the values in following method:
#Configuration
#PropertySource("classpath:application.properties")
public class MyDataSource {
#Value("${db.driver}")
private String DB_DRIVER;
#Value("${db.url}")
private String DB_URL;
#Value("${db.username}")
private String DB_USERNAME;
#Value("${db.password}")
private String DB_PASSWORD;
#Bean
public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(DB_DRIVER);
dataSource.setUrl(DB_URL);
dataSource.setUsername(DB_USERNAME);
dataSource.setPassword(DB_PASSWORD);
return dataSource;
}
}
My application.properties is in main/resources folder and values can be seen in variables in debug mode. But on running app, it shows Property ' ' must not be empty.
EDIT: I am not sure what can be the issue in first case?
So changed the application.property file as suggested and code as below :
#Autowired
protected JdbcTemplate jdbcTemp;
public List<> getData(String id) {
return jdbcTemp.query("SELECT ........,new RowMapper());
}
But getting java.lang.NullPointerException:
If you're using Spring Boot, you can leverage application.properties file by declaring some entries:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
In this way there is no need to implement a #Configuration class to setup database connection in Spring Boot.
You can deepen more here:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-sql.html
By the way, take a look at spring.io
For the java configuration, using Environment instance to obtain the properties seems to be the preferred way, as by default ${..} placeholders are not resolved.
You may use something like this:
#Autowired
private Environment env;
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("db.driver");
.....
return dataSource;
}
Reasons from the Spring Jira:
it's inconsistent. #PropertySource is the declarative counterpart to ConfigurableEnvironment#addPropertySource. We do not add a
PropertySourcesPlaceholderConfigurer in the latter case, and it would
be inconsistent to do so in the former. it will not be what the user
intended in every (or even most) cases.
It is entirely possible, and even recommended that #Configuration class users forego $ {...} property replacement entirely, in favor of
Environment#getProperty lookups within #Bean methods. For users
following this recommendation, the automatic registration of a
PropertySorucesPlaceholderConfigurer would be confusing when noticed,
and generally undesirable as it's one more moving part. Yes, it's
presence is benign, but not cost-free. a PSPC must visit every bean
definition in the container to interrogate PropertyValues, only to do
nothing in cases where users are going with the
Environment#getProperty approach.
it is solvable (and already solved) by documentation. Proper use of #PropertySource, PropertySourcesPlaceholderConfigurer and other
components is pretty comprehensively documented in the Javadoc for
#Configuration already, and reference documentation is soon to follow.
Me too was getting the error when tried to switch from MySQL to MSSQL. The actual issue was I forgot to put the MSSQL dependency in the service. I used mssql-jdbc

Categories

Resources