Using Spring 4, I've got the following test setup:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = JpaConfig.class)
#ActiveProfiles(resolver = TestResolver.class)
public class SimpleTest {
The TestResolver has been implemented as:
public class TestResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> aClass) {
String[] profiles = new String[1];
profiles[0] = "test";
return profiles;
}
}
JpaConfig has been annotated with PropertySource
#Configuration
#PropertySource("classpath:properties/application-${spring.profiles.active:dev}.properties")
#EnableJpaRepositories(basePackages={"com.my.namespace.repositories"})
public class JpaConfig {
Whenever I run the SimpleTest it tries to locate: properties/application-dev.properties while I expected it to be properties/application-test.properties.
What's I'm trying to accomplish here has been based on the following post: Spring integration tests with profile
I believe this is, actually, the issue you are facing. And in that same post you have an explanation from Dave Syer and a possible solution from another user. To follow Dave's advice, this would be a possible implementation of an ApplicationContextInitializer:
public class MyApplicationContextInitializer implements
ApplicationContextInitializer<GenericApplicationContext> {
public void initialize(GenericApplicationContext context) {
context.getEnvironment().getSystemProperties().put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "some_profile");
}
}
and on your test class:
#ContextConfiguration(classes = JpaConfig.class, initializers = MyApplicationContextInitializer.class)
But I would say that the suggested approach (with different .properties files loaded for different profiles) in that SO post is a more elegant approach.
I think you should change #PropertySource to:
#PropertySource("classpath:properties/application-${spring.profiles.active}.properties")
Also for simplicity (shouldn't have any effect on how the code runs) your #ActiveProfile could be
#ActiveProfiles("test")
Related
I have problems with injection #Value('${mybean.secret}') property into my bean during tests in Spock with Spring Boot & Groovy.
I have a very simple test class MyBeanTest
#ContextConfiguration(classes = [
MyAppConfig
])
#PropertySource([
"classpath:context/default-properties.yml"
])
class MyBeanTest extends Specification {
#Autowired
MyBean myBean
def "should populate properties "() {
expect:
myBean.secretProperty == "iCantTellYou"
}
}
And MyAppConfig.groovy as this:
#Configuration
class MyAppConfig {
#Bean
MyBean credential(#Value('${mybean.secret}') String secret) {
return new MyBean(secret)
}
}
When I run tests the value that is injected into secret is simply ${mybean.secret}.
The real value is not injected from properties file I enclose on test specification.
I'm using single-quote on #Value because of Groovy. Double quote with $ sign makes it being processed by groovy GString mechanism.
However, the problem doesn't occur on regular application run.
If I start application and put the breakpoint on the MyAppConfig#credential method the secret value is correctly read from the properties file, which are configured as follow:
#Configuration
#PropertySource(["classpath:context/default-properties.yml"])
class PropertiesConfig {
}
When I specify property from hand like this:
#TestPropertySource(properties = [
"mybean.secret=xyz"
])
class MyBeanTest extends Specification {
It works. The property is read. But it's not my goal, cause there's much more properties in the project and it would become cumbersone to define them everywhere from hand.
Can you spot the problem I am missing in this code?
The missing puzzle was YamlPropertySourceFactory.
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
I was using yml properties files with nested properties:
mybean:
secret: xyz
And Spring was not loading them correctly.
I had to update #PropertySource annotation as well as follow:
#Configuration
#PropertySource(
value = ["classpath:context/default-properties.yml"],
factory = YamlPropertySourceFactory.class
)
class PropertiesConfig {
}
Now it works like a charm 😏.
I learned this on Baeldung website => https://www.baeldung.com/spring-yaml-propertysource
As I understand that if we use spring stereotypes then we don't need to use new keyword to create an instance. Spring manages that for us and provide us with the beans at runtime.
And in order for Spring to inject those beans we need to use #Autowired annotation where we want Spring to inject that bean.
Below I have a very simple class where I am using #Component so that spring manages that. This class has one List which I am initializing with my own responsibility and then a small method which does some logic.
#Slf4j
#Data
#NoArgsConstructor
#AllArgsConstructor
#Component
public class Parser {
private List<String> strList = new ArrayList<>();
public void parseStrings(final String[] strs) {
Arrays.stream(strs)
.map(String::toLowerCase)
.filter(str -> str.length() > 8)
.filter(str -> str.endsWith("sam"))
.forEach(sam1 -> { strList.add(sam1); });
}
}
I also wrote one unit test to test that and here is that.
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.*;
#RunWith(MockitoJUnitRunner.class)
class ParserTest {
#Autowired
private Parser parser;
#Test
void parseStrings() {
String str[] = {"abcsamsam", "abcsyjhgfed abdul sam","abcAhgbkgdjhul samad", "abcabjhgdulsamsam", "sa"};
parser.parseStrings(str);
assertTrue(parser.getStrList().size() == 3);
assertTrue(parser.getStrList().get(0).equalsIgnoreCase("abcsamsam"));
}
}
The test fails with
java.lang.NullPointerException when it tries to call parseStrings method which means that its not able to inject a proper initialized bean at run time.
Can some one guide that what I am missing?
Is it necessary to add constructors (which here I am doing using lombok annotations) when using spring stereotypes on a class.
I don't see any mock created so why you are using #RunWith(MockitoJUnitRunner.class)?
I've seen as well answers recommending the use of #SpringBooTest. This annotation loads the whole context of your application basically for integration tests in order to integrate different layers of the application. That also means no mocking is involved. Do you really need that? (I don't think so since you're talking about unit test)
If your parser doesn't reference any other Bean (which need to be mocked), then you are in case of simple unit test.
#RunWith(SpringRunner.class) // you can even removed it
class ParserTest {
private Parser parser;
#Before
public void setUp() {
parser = new Parser();
}
#Test
void parseStrings() {
String str[] = {"abcsamsam", "abcsyjhgfed abdul sam","abcAhgbkgdjhul samad", "abcabjhgdulsamsam", "sa"};
parser.parseStrings(str);
assertTrue(parser.getStrList().size() == 3);
assertTrue(parser.getStrList().get(0).equalsIgnoreCase("abcsamsam"));
}
Spring Autowire if you run the test case with SpringRunner. So modify the test class as follows.
#RunWith(SpringRunner.class)
class ParserTest {
}
To answer your second question,
No, it is not necessary to add no-argument constructor unless you also have a parameterised constructor in the same class. In that case you need to explicitly add a no-arg constructor.
Why so you even need MockitoJUnitRunner here? The Parser has no dependencies. A simple initialization in the test will be enough. Just inialize the Parser instead of using an annotation. #SpringBootTest is meant for integration tests. It brings in the Spring context and makes your unit test slow & bulky.
In my case the class was not declared public
This should work:
#SpringBootTest
#RunWith(SpringRunner.class)
class ParserTest {
#Autowired
private Parser parser;
#Test
void parseStrings() {
String str[] = {"abcsamsam", "abcsyjhgfed abdul sam","abcAhgbkgdjhul samad", "abcabjhgdulsamsam", "sa"};
parser.parseStrings(str);
assertTrue(parser.getStrList().size() == 3);
assertTrue(parser.getStrList().get(0).equalsIgnoreCase("abcsamsam"));
}
}
SpringBoot2,you can use the annotation:#SpringBootTest to do unit test.
just like below case:
#SpringBootTest
class DemoApplicationTests {
#Test
void contextLoads() {
}
}
I am learning Junit 5 and test cases.
I am using spring boot version '2.2.6.RELEASE and JUnit 5,
in my application, I have a method that processes based on the boolean flag from property file.
\src\main\resources\application.properties
#data base connection properties
spring.app.datasource.url=jdbc:mysql://localhost:3306/student_db
spring.app.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.app.datasource.username=root
spring.datasource.password=root
spring.app.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
#additional properties
spring.property.name=shrikant
spring.property.enable=false
database connection properties are used to create the database connection
Datasource.java
#Value("${spring.app.datasource.url}")
private String url;
#Value("${spring.app.datasource.driver-class-name}")
private String className;
#Value("${spring.app.datasource.username}")
private String userName;
#Value("${spring.datasource.password}")
private String password;
#Value("${spring.app.jpa.properties.hibernate.dialect}")
private String dialect;
controller class
#RestController
public class Controller {
#Value("${spring.property.name}")
private String name;
#Value("${spring.property.enable}")
private boolean status;
public void validateObject(String surName) {
if (status) { # if this flag is true then only process
System.out.println("name= " + name);
System.out.println("surName= " + surName);
}
}
ControllerTest.java
#SpringBootTest
class ControllerTest {
#Autowired
private Controller controller;
#Test
void show() {
controller.validateObject("sharma");
}
by default the flag is false, so every time test case runs it never processes the object.
so I tried to create aplication.properties in the test folder
\src\test\resources\application.properties
spring.property.name=vishal
spring.property.enable=true
but now it's giving me an error that
Could not resolve placeholder 'spring.app.datasource.url'
but I don't want to provide DB connection URL, I am not connecting to the database while testing.
Q1 - how to change the value of properties file for test case only.
Q2 - is it mandatory to provide all the keys of \src\main\resources\application.properties is \src\test\resources\application.properties?
I am new in test case, so little explained answers would be welcomed.
Update:-
I found that
#SpringBootTest
#TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"})
class ControllerTest {
will solve the issue temporarily, by providing keys along with values, but I have a lot of keys, which cannot be provided in such a way.
If you use #SpringBootTest then your test will load the whole Spring context. This means it will create all your beans and try to wire them together. If you inject property values to your beans, you have to specify them all for such tests as otherwise, you won't be able to boot the application.
What might help you in such a situation is to use test annotations like #WebMvcTest or #DataJpaTest to focus on testing just slices of your application. Using #WebMvcTest you'll get an application context just containing controllers and everything related to your web layer. Other beans (like service classes) can be mocked with #MockedBean.
Next, for testing business logic in service classes try not to use #SpringBootTest and rather rely on plain JUnit 5 and Mockito to verify your code.
You still might want to have some integration tests that use #SpringBootTest to make sure everything is working together. In such case, you can hard code any static property inside application.properties in src/test/resources/ or using the annotation like you already did: #TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"}).
When it comes to providing a database, you can either configure an embedded database (which I would try to avoid) or use the same database as in production. Testcontainers helps you a lot when it comes to providing external infrastructure for your tests like a database.
An example setup with Spring Boot >= 2.2.6 and JUnit might look like the following:
#Testcontainers
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
#Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withPassword("password")
.withUsername("username");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
}
#Test
public void contextLoads() {
}
}
For Junit5, annotate your test class with path to application.properties in your src/main/resources:
#ExtendWith(SpringExtension.class)
#TestPropertySource("classpath:application.properties")
public class MyTest {
#Value("${property.name}")
private int myProperty;
tests...
}
property gets loaded
I have list of properties in my yml for eachplatform tv and android
i have created one factory class where we provide the platform and you will get the properties , using the referring link "https://andrewzc.com/loading-configuration-properties-in-spring-boot-test/" .
but on running the test the property object is coming as null . How to load properties in junit from properties file ?
#Component
public class MasterPropertiesFactory {
#Autowired
private TvProperties tv ;
#Autowired
private AndroidProperties android ;
public MasterPropertiesClass getProperty(String platform){
if(platform.equals("tv")){
return tv;
}else if(platform.equals("android")){
return android;
}
}
}
Now i am writing a junit to read these properties but on running test getPaginatedContent it is coming as null .
//PageTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {TestApp.class ,MasterPropertiesFactory.class }, initializers = ConfigFileApplicationContextInitializer.class)
class PageTest {
#Autowired
MasterPropertiesFactory masterPropertiesFactory;
#Test
void getPaginatedContent() {
Assert.assertNotNull(masterPropertiesFactory);
}
}
// TestApp.java
#EnableAutoConfiguration
#EnableConfigurationProperties(value = {MasterPropertiesFactory.class })
public class TestApp {}
There're several points you should fix in your code:
There should be Configuration class for creating TvProperties and AndroidProperties
Either you should use that class in PageTest - ContextConfigurationor you should declare custom Configuration class for the test, to load the properties.
#ContextConfiguration(classes = {TestApp.class ,MasterPropertiesFactory.class }is not good idea, because, it should contain information about Configuration classes.
For #Autowired
MasterPropertiesFactory masterPropertiesFactory; somewhere you should create bean for that (preferred way is in test Conifugration)
For one of my Spring beans(say Application class), I'm fetching the value of a property(my.property.flag=true/false) from a properties file(prop.properties) using #Value annotation. That works perfectly fine.
I need to write an integration test(say ApplicationIt class) where I need to test with both the values of the property i.e. for both true and false.
In my properties file, the value of the property is set to true. Is it possible to set the value dynamically to false from my Integration test?
For Example,
prop.properties:
my.property.flag=true
Application class file:
#Component
class Application {
//This value is fetched from properties file
//the value is set to true.
#Value(${my.property.flag})
private String isTrue;
......
..........
}
Integration Test:
class ApplicationIT {
//how can I set the value of isTrue here to false?
}
You can specify test properties on the test class as follows:
#RunWith(SpringRunner.class)
#TestPropertySource(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
public class MyTest {
Since Spring has a whole hierarchy of property overrides, this works pretty well, the downside being you need separate test classes for different values. If you're using Spring Boot, there's another annotation that provides the same functionality but also has more options for configuring your test environment. Example:
#SpringBootTest(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
Again, you will need separate test classes to handle hard-coded test properties.
I was bugged with this for a while and found this neat way to override the properties. It is quite useful if you need some programmatic initialization of the application context such as registering property sources like in that case but not only. The following approach uses ContextConfiguration's initializers.
example for Spring Boot 1.5.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(ctx,
"spring.redis.host=" + redis.getContainerIpAddress(),
"spring.redis.port=" + redis.getMappedPort(REDIS_PORT));
}
}
}
example for Spring Boot 2.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertyValues.of(
"spring.redis.host:" + redis.getContainerIpAddress(),
"spring.redis.port:" + redis.getMappedPort(REDIS_PORT))
.applyTo(ctx);
}
}
}
I want to mention good old reflection way. You can use spring provided utility class for it after you wired in your component:
ReflectionTestUtils.setField(component, "isTrue", true)
You can change it to any value you want in consequent tests
Preferably, use constructor injection instead of field injection:
#Component
class Application {
Application(#Value("${my.property.flag}") boolean flag) {
...
}
}
This makes using mocks or test values as simple as passing an argument.