This is my first time working with Externalized Configuration and yaml.
I created a yaml where I use the class name as KEY, and the field name as VALUE
YAML:
project:
test:
service:
computator:
# exclude field from beeing randomly valorized
population:
exclude:
InputClass: 'myDate'
AnotherClass: 'myName'
ExcludePopulationProperties:
#Data
#Component
#ConfigurationProperties(prefix = "project")
public class ExcludePopulationProperties {
private Test test;
#Data
public static class Test {
private Service service;
}
#Data
public static class Service {
private Computator computator;
}
#Data
public static class Computator {
private Population population;
}
#Data
public static class Population {
private Map<String, String> exclude;
}
}
Test with JUnit 5:
#ContextConfiguration(classes = { ExcludePopulationProperties.class })
#ExtendWith(SpringExtension.class)
class YamlTest {
#Autowired
private ExcludePopulationProperties excludePopulationProperties;
#Test
void testExternalConfiguration() {
Map<String, String> map = excludePopulationProperties.getTest().getService().getComputator().getPopulation().getExclude();
assertNotNull(map);
}
The problem is that I have a NullPointerException because test is null
So I'm not sure what is wrong here, I was expecting that the map was correctly populated.
I also tried to add
#TestPropertySource(properties = { "spring.config.location=classpath:application-_test.yaml" })
on the YamlTest
Your ExcludePopulationProperties class should not be annotated with #Component. Instead, you should have the annotation #EnableConfigurationProperties(ExcludePopulationProperties.class) on a configuration class in your project (the main application class will work).
Change your properties class to look like this (removing #Component):
#Data
#ConfigurationProperties(prefix = "project")
public class ExcludePopulationProperties {
...
}
Change your application class to enable the configuration properties:
#SpringBootApplication
#EnableConfigurationProperties(ExcludePopulationProperties.class)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
And the YamlTest class to look like this (using #SpringBootTest):
#SpringBootTest
#ContextConfiguration(classes = { DemoApplication.class })
#TestPropertySource(properties = { "spring.config.location=classpath:application-test.yaml" })
class YamlTest {
#Autowired
private ExcludePopulationProperties excludePopulationProperties;
#Test
void testExternalConfiguration() {
Map<String, String> map = excludePopulationProperties.getTest().getService().getComputator().getPopulation().getExclude();
assertNotNull(map);
assertEquals(map.get("InputClass"), "myDate");
assertEquals(map.get("AnotherClass"), "myName");
}
}
With these little changes, now I'm able to test the properties from YAML file.
I improved the yaml a little bit:
# test placeholders
project:
test:
service:
computator:
# exclude field from beeing randomly valorized
population:
exclude:
InputClass:
- 'myDate'
AnotherClass:
- 'myName'
so now the ExcludePopulationProperties have a Map<String, List<String>> instead of Map<String, String>, in this way I will be able to exclude more than one field from the same class:
#Data
#Configuration
#ConfigurationProperties(prefix = "project")
#PropertySource(value = "classpath:application-_test.yaml", factory = YamlPropertySourceFactory.class)
public class ExcludePopulationProperties {
private Test test;
#Data
public static class Test {
private Service service;
}
#Data
public static class Service {
private Computator computator;
}
#Data
public static class Computator {
private Population population;
}
#Data
public static class Population {
private Map<String, List<String>> exclude;
}
}
YamlPropertySourceFactory is a class implemented by Baeldung in this guide:
#PropertySource with YAML Files in Spring Boot
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(resource.getResource().getFilename(), properties);
}
}
Test Class :
#EnableConfigurationProperties
#ContextConfiguration(classes = { ExcludePopulationProperties.class })
#TestPropertySource(properties = { "spring.config.location=classpath:application-_test.yaml" })
#ExtendWith(SpringExtension.class)
class YamlTest {
#Autowired
private ExcludePopulationProperties excludePopulationProperties;
#Test
void testExternalConfiguration() {
Map<String, List<String>> map = excludePopulationProperties.getTest().getService().getComputator().getPopulation().getExclude();
assertNotNull(map);
}
}
Please note that for Mockito you need to use both, SpringExtension and MockitoExtension:
#EnableConfigurationProperties
#ContextConfiguration(classes = { ExcludePopulationProperties.class })
#Extensions({
#ExtendWith(SpringExtension.class),
#ExtendWith(MockitoExtension.class)
})
class YamlTest {
}
UPDATE
I find a better solution in order to avoid writing the annotations on all test classes.
add the jackson jackson-dataformat-yaml dependency
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson-dataformat-yaml.version}</version>
</dependency>
The configuration properties class will be:
#Data
public class ExcludePopulationProperties {
private Project project;
#Data
public static class Project {
private Test test;
}
#Data
public static class Test {
private Service service;
}
#Data
public static class Service {
private Computator computator;
}
#Data
public static class Computator {
private Population population;
}
#Data
public static class Population {
private Map<String, List<String>> exclude;
}
public static ExcludePopulationProperties build() throws IOException {
InputStream inputStream = new FileInputStream(new File("./src/test/resources/" + "application-_test.yaml"));
YAMLMapper mapper = new YAMLMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
return mapper.readValue(inputStream, ExcludePopulationProperties.class);
}
}
then, wherever you need, simply call the static build method, the test class will be more simple :
#ExtendWith(SpringExtension.class)
class YamlTest {
#Test
void testExternalConfiguration() throws IOException {
Map<String, List<String>> map = ExcludePopulationProperties.build().getProject().getTest().getService().getComputator().getPopulation().getExclude();
assertNotNull(map);
}
}
Related
I have following code that is getting data from application yaml file and setting it into static class variables
I have tried many different annotations to assert the config data in unit test but none seems to work
Can any one point me to right way to unit test the following code
I have tried following annotations with their specific unit test code.
#RunWith(SpringRunner.class)
#EnableConfigurationProperties(AppConfig.class)
#TestPropertySource("classpath:application.yml")
OR
#ContextConfiguration(classes = TestApp.class, initializers = ConfigFileApplicationContextInitializer.class)
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = “server.config")
public class AppConfig {
private MyServerConfig myServer = new MyServerConfig();
#Getter
#Setter
public static class MyServerConfig {
private String serverId;
private String secret;
}
}
Application.yml
server:
config:
myServerConfig:
serverId: “1234”
secret: “123456789”
Unit Test
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = TestApp.class, initializers = ConfigFileApplicationContextInitializer.class)
public class AppConfigTest {
#Configuration
#EnableConfigurationProperties(AppConfig.class)
public static class Config { }
#Autowired
private AppConfig appConfig;
#BeforeMethod
public void setup() throws IOException {
appConfig = new AppConfig();
}
#Test
public void Check_Config_object_Success() {
Assert.isInstanceOf(AppConfig.MyServerConfig.class, appConfig.getMyServerConfig());
assertEquals("1234",appConfig.getMyServerConfig().getServerId()); ---> gettin null value here
}
I currently have the following situation where elements should be filtered based on a subscription matrix which is divided onto multiple yaml files. For different processes different subscription filters shall be used.
The yamls look like the following:
process1.yaml
subscriptions:
-
attributeA: ...
attributeB: ...
-
attributeA: ...
attributeB: ...
...
process2.yaml
subscriptions:
-
attributeA: ...
attributeB: ...
-
attributeA: ...
attributeB: ...
...
Following this Baeldung link I created a YamplPropertySourceFactory and use it the following way:
#Data
#ConfigurationProperties
#EnableConfigurationProperties
public abstract class ProcessConfig {
private List<Subscription> subscriptions;
#Data
public static class Subscription {
private String attributeA;
private String attributeD;
}
static class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public org.springframework.core.env.PropertySource<?> createPropertySource(#Nullable String name, EncodedResource resource) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
Properties properties = factory.getObject();
//noinspection ConstantConditions
return new PropertiesPropertySource(resource.getResource().getFilename(), properties);
}
}
#Configuration("process1Config")
#PropertySource(value = "classpath:processmatrix/process1.yaml", factory = YamlPropertySourceFactory.class)
public static class Process1Config extends ProcessConfig {
}
#Configuration("process2Config")
#PropertySource(value = "classpath:processmatrix/process2.yaml", factory = YamlPropertySourceFactory.class)
public static class Process2Config extends ProcessConfig {
}
...
}
The issue occourring now is that the bean creation for both configurations is called on application start but only one configuration is received when autowiring in usage.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Process1Config.class, Process2Config.class, ...})
public class ProcessConfigTest {
#Autowired
#Qualifier("process1Config")
Process1Config process1Config;
#Autowired
#Qualifier("process2Config")
Process1Config process2Config;
#Test
public void testProcess1Size() {
assertEquals(130, process1Config.getSubscriptions().size());
}
#Test
public void testProcess2Size() {
assertEquals(86, process2Config.getSubscriptions().size());
}
}
If the tests are run testProcess1Size() will pass while testProcess2Size() fails with the subscriptions().size() beeing 130.
Do you have any idea what the issue could be and how I could load multiple yaml configurations? I already tried to set the #Scope("prototype") but it didn't help. Also I tried to remove the common parent class but that didn't help either.
I have a spring project in Java.
In the project, I have a Kafka configuration class and an additional class that uses the Kafka templets and performs some logic.
I am trying to create a unit test to verify my logic but I am not able to #Autowire the class in the test class.
Here are my classes:
Kafka configuration
#Configuration
#Getter
#PropertySource(value = "classpath:realtime.properties")
public class KafkaProducerConfig {
#Value("${kafka.producer.client.id}")
private String producerClientId;
#Value("${kafka.url}")
private String kafkaUrl;
#Value("${kafka.retries.config}")
private int retryConfig;
#Bean
public ProducerFactory<Void, SomeClass1> producerFactory1() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaUrl);
configProps.put(ProducerConfig.CLIENT_ID_CONFIG, producerClientId.concat("_someClass1"));
configProps.put(ProducerConfig.RETRIES_CONFIG, retryConfig);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, VoidSerializer.class.getName());
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, AvroSerde.serializerFor(SomeClass1.class));
return new DefaultKafkaProducerFactory<>(configProps);
}
#Bean
public KafkaTemplate<Void, SomeClass1> kafkaTemplate1() {
return new KafkaTemplate<>(producerFactory1());
}
#Bean
public ProducerFactory<Void, someClass2> producerFactory2() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaUrl);
configProps.put(ProducerConfig.CLIENT_ID_CONFIG, producerClientId.concat("_someClass2"));
configProps.put(ProducerConfig.RETRIES_CONFIG, retryConfig);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, VoidSerializer.class.getName());
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, AvroSerde.serializerFor(someClass2.class));
return new DefaultKafkaProducerFactory<>(configProps);
}
#Bean
public KafkaTemplate<Void, someClass2> kafkaTemplate2() {
return new KafkaTemplate<>(producerFactory2());
}
}
The class who uses Kafka configuration:
#Component
public class FilterPublisher {
private final String SERVICE_NAME = "some-service";
#Value("${kafka.notification.topic}")
private String notificationTopic;
#Value("${kafka.tracing.topic}")
private String tracingTopic;
#Autowired
private KafkaTemplate<Void, someClass1> kafkaTemplate1;
#Autowired
private KafkaTemplate<Void, someClass2> kafkaTemplate2;
public FilterPublisher() {
}
public void doSomeLogic() {
// do something ...
}
private void publishToKafka() {
// do something ....
}
}
The unit test class:
#TestPropertySource(locations = "classpath:application-test.properties")
#SpringBootTest(classes = {FilterPublisher.class, KafkaProducerConfig.class})
public class FilterPublisherTest {
#Autowired
private FilterPublisher filterPublisher;
#Test
public void tmpTest() {
filterPublisher.processConfigurations();
}
}
When running the test, filterPublisher is always null.
What am I missing? what am I doing wrong? how do I fix it?
I also tried using the import annotation on the test class #Import(TestConfig.class)
#TestConfiguration
public class TestConfig {
#Bean
public FilterPublisher filterPublisher() {
return new FilterPublisher();
}
}
but the result was the same.
Any help would be appreciated, thank you.
Finally got it working,
here is the code for the Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {FilterPublisher.class, KafkaProducerConfig.class})
public class FilterPublisherTest {
#Autowired
ApplicationContext context;
#Autowired
FilterPublisher filterPublisher;
// ALL TEST METHODS GO HERE
}
i have a question here, please give some ideas.
I have two beans. FaceComparisonServerImpl depends on FaceServer.
When i want to test. I want to change the String in my 'FaceServer' bean.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
#Autowired
private FaceServer faceServer;
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
...
}
}
#Component
public class FaceServer {
#Autowired
private RestTemplate restTemplate;
//Not final, just to test.
private String version = "1.0";
private static final String CODE = "code";
private static final String MESSAGE = "message";
//Final
private static final String SERVER_URL = "http://127.0.0.1:8066/api/ZKComparison";
}
Bellow is my test code.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
#Test
public void getServerInfo(){
//How can i modify the value of SERVER_URL in faceServer?
FaceComparsionInfo serverInfo = faceComparisonServer.getServerInfo();
System.out.println(serverInfo);
}
}
My question is:
How can i modified the value of 'version' and 'SERVER_URL' in #Bean(faceServer)?
Thanks you!
You need create FaceServer mock bean for test configuration.
And override required methods
#Configuration
Class TestConfig{
#Bean
#Primary
public FaceServer faceServer() {
return new FaceServer() {
#override
public String getServerInfo(){
return "required info";
}
};
}
}
The easiest way to customize the values is to make them Spring properties:
#Component
public class FaceServer {
#Value("${faceServer.version}")
private String version;
#Value("${faceServer.url}")
private String serverUrl;
// ...
}
You can either have default values for the #Value annotations or use some default property values in application.yml.
Now just override those properties in your test with the values you want:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = TestConfig.class)
#TestPropertySource(properties = {
"faceServer.version=1.0",
"faceServer.url=http://127.0.0.1:8066/api/ZKComparison"
})
public class FaceServerTestByTyler {
#Autowired
private FaceComparisonServer faceComparisonServer;
// ...
}
However...
The second option is to make your classes more unit-testable. Prefer construction injection over field injection, and you can test your classes more independently.
#Service
public class FaceComparisonServerImpl implements FaceComparisonServer {
private final FaceServer faceServer;
public FaceComparisonServerImpl(FaceServer faceServer) {
this.faceServer = faceServer;
}
#Override
public FaceComparsionInfo getServerInfo() {
String serverInfo = faceServer.getServerInfo();
// ...
}
}
This now becomes unit-testable:
public class FaceServerTestByTyler {
private FaceComparisonServer faceComparisonServer;
private FaceServer faceServer;
#BeforeEach
public setup() {
faceServer = mock(FaceServer.class);
faceComparisonServer = new FaceComparisonServer(faceServer);
}
#Test
public void getServerInfo() {
when(faceServer.getServerInfo()).thenReturn(xxx);
// ...
}
}
The second option ends up with a test that runs much faster than any solutions that suggest to create a mock bean through a test configuration.
I have an application which needs a service to be Spring wired in a JsonDeserializer. The problem is that when I start up the application normally it is wired, but when I start it up in a test, it is null.
The relevant code is:
JSON Serializer/Deserializer:
#Component
public class CountryJsonSupport {
#Component
public static class Deserializer extends JsonDeserializer<Country> {
#Autowired
private CountryService service;
#Override
public Country deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
return service.getById(jsonParser.getValueAsLong());
}
}
}
Domain Object:
public class BookingLine extends AbstractEntity implements TelEntity {
.....other fields
//Hibernate annotations here....
#JsonDeserialize(using = CountryJsonSupport.Deserializer.class)
private Country targetingCountry;
..... other fields
}
Test Class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(Application.class)
#WebIntegrationTest({"server.port=0"})
#ActiveProfiles("test")
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class BookingAndLinesControllerFunctionalTest {
#Test
public void testGetBooking() {
Booking booking = bookingRepositoryHelper.createBooking();
bookingRepository.save(booking);
String uri = String.format("http://localhost:%s/api/v1/booking-and-lines/" + booking.getBookingCode(), port);
Booking booking1 = restTemplate.getForObject(uri, Booking.class); // line which falls over because countryService is null
}
}
Any ideas?
Managed to discover the answer to this one after fiddling around long enough. Just needed some config like this:
#Configuration
#Profile("test")
public class TestConfig {
#Bean
public HandlerInstantiator handlerInstantiator() {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(HandlerInstantiator handlerInstantiator) {
Jackson2ObjectMapperBuilder result = new Jackson2ObjectMapperBuilder();
result.handlerInstantiator(handlerInstantiator);
return result;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(Jackson2ObjectMapperBuilder objectMapperBuilder) {
return new MappingJackson2HttpMessageConverter(objectMapperBuilder.build());
}
#Bean
public RestTemplate restTemplate(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
List<HttpMessageConverter<?>> messageConverterList = new ArrayList<>();
messageConverterList.add(mappingJackson2HttpMessageConverter);
return new RestTemplate(messageConverterList);
}
}