I'm using Spring boot - 2.3.3.RELEASE. There are some values in application.yaml which I'm trying to inject in the classes using #Value annotation. But for some reason, they are not loading up. The result should be that, in SendPhoneByPhoneNumbers.java, we should be able to read notificationServiceURL from application.yaml.
Note- I'm using Factory and Strategy pattern. This project is going to be used as a Library for other projects to import and use the methods exposed by Service layer.
Here is the folder structure: https://imgur.com/a/jYr7wyP
I'm trying to test by running Demo.java in Debug mode to see how the actual values looks like.
application.yaml
notificationService:
url: "https://someURL.com"
Demo.java
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
#SpringBootApplication
public class Demo {
public static void main(String[] args) {
SpringApplication.run(Demo.class, args);
String title="Title";
String message="message";
List<String> phoneNumbers = new ArrayList<>();
phoneNumbers.add("333-222-1111");
PhoneService phoneService = new PhoneService();
phoneService.sendNotificationByPhoneNumbers(title, message, phoneNumbers);
}
}
PhoneService.java
#Service
public class PhoneService {
PhoneServiceImpl notificationServiceImpl = new PhoneServiceImpl();
public void sendNotificationByPhoneNumbers(String title, String message, List<String> phoneNumbers) {
notificationServiceImpl.sendNotificationByPhoneNumbers(title, message, phoneNumbers);
}
}
PhoneServiceImpl.java
#Slf4j
#Component
public class PhoneServiceImpl {
#Value("${notificationService.url}")
String url;
public void sendNotificationByPhoneNumbers(String title, String message, List<String> phoneNumbers) {
PhoneContext phoneContext = new PhoneContext(new SendPhoneByPhoneNumbers(url));
phoneContext.notify(title, message, phoneNumbers);
}
}
PhoneContext.java
public class PhoneContext {
private PhoneStrategy phoneStrategy;
public PhoneContext(PhoneStrategy phoneStrategy){
this.phoneStrategy = phoneStrategy;
}
public void notify(String title, String message, List<String> employees){
phoneStrategy.sendNotification(title, message, employees);
}
}
PhoneStrategy.java
public interface PhoneStrategy {
public void sendNotification(String title, String message, List<String> listOfEmployeeIdGroupNamePhoneNumbers);
}
SendPhoneByPhoneNumbers.java
#Slf4j
public class SendPhoneByPhoneNumbers implements PhoneStrategy {
RestTemplate restTemplate;
String notificationServiceURL;
BuildHttpRequest buildHttpRequest;
public SendPhoneByPhoneNumbers(String notificationServiceURL) {
this.notificationServiceURL = notificationServiceURL;
this.restTemplate = new RestTemplate();
this.buildHttpRequest = new BuildHttpRequest();
}
#Async
public void sendNotification(String title, String message, List<String> phoneNumbers) {
SmsMessage smsMessage= new SmsMessage(title, message, phoneNumbers, Collections.emptyList(), Collections.emptyList());
try {
HttpHeaders headers = new HttpHeaders();
headers.set("idToken", buildHttpRequest.getNewToken());
HttpEntity<SmsMessage> newRequest = new HttpEntity<>(smsMessage, headers);
restTemplate.postForObject(notificationServiceURL + "/someUrl", newRequest, String.class);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
Also, if you guys have any suggestions on modifying the code/structure in an way to make it better, please do suggest.
Thanks in advance.
PhoneService phoneService = new PhoneService();
Since PhoneService that you are using is not a managed bean by spring container, the values are not being injected.
Code Improvement and FIX
String title="Title";
String message="message";
List<String> phoneNumbers = new ArrayList<>();
phoneNumbers.add("333-222-1111");
// PhoneService phoneService = new PhoneService();
phoneService.sendNotificationByPhoneNumbers(title, message, phoneNumbers);
Move this code to a class implementing CommanLineRunner or ApplicationRunner and override corresponding run(). In this class, you could #Autowire PhoneService rather than manually instantiating it. Also note that you have to mark this class with #Component
Other minor suggested changes:
You can make SendPhoneByPhoneNumbers class a singleton. If there are multiple implementions, use #Qualifier
Both RestTemplate and BuildHttpRequest could be created using #Bean annotation.
Since you are using lombok, using #RequiredArgsConstructor could also be considered.
Related
Small question on Spring Boot, and how to use a design pattern combined with Spring #Value configuration in order to select the appropriate #Repository please.
Setup: A springboot project which does nothing but save a pojo. The "difficulty" is the need to choose where to save the pojo, based on some info from inside the payload request.
I started with a first straightforward version, which looks like this:
#RestController
public class ControllerVersionOne {
#Autowired private ElasticRepository elasticRepository;
#Autowired private MongoDbRepository mongoRepository;
#Autowired private RedisRepository redisRepository;
//imagine many more other repositories
//imagine many more other repositories
//imagine many more other repositories
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
if (whereToSave.equals("elastic")) {
return elasticRepository.save(myPojo).toString();
} else if (whereToSave.equals("mongo")) {
return mongoRepository.save(myPojo).toString();
} else if (whereToSave.equals("redis")) {
return redisRepository.save(myPojo).toString();
// imagine many more if
// imagine many more if
// imagine many more if
} else {
return "unknown destination";
}
}
With the appropriate #Configuration and #Repository for each and every databases. I am showing 3 here, but imagine many. The project has a way to inject future #Configuration and #Repository as well (the question is not here actually)
#Configuration
public class ElasticConfiguration extends ElasticsearchConfiguration {
#Repository
public interface ElasticRepository extends CrudRepository<MyPojo, String> {
#Configuration
public class MongoConfiguration extends AbstractMongoClientConfiguration {
#Repository
public interface MongoDbRepository extends MongoRepository<MyPojo, String> {
#Configuration
public class RedisConfiguration {
#Repository
public interface RedisRepository {
Please note, some of the repositories are not children of CrudRepository. There is no direct ___Repository which can cover everything.
And this first version is working fine. Very happy, meaning I am able to save the pojo to where it should be saved, as I am getting the correct repository bean, using this if else structure.
In my opinion, this structure is not very elegant (if it ok if we have different opinion here), especially, not flexible at all (need to hardcode each and every possible repository, again imagine many).
This is why I went to refactor and change to this second version:
#RestController
public class ControllerVersionTwo {
private ElasticRepository elasticRepository;
private MongoDbRepository mongoRepository;
private RedisRepository redisRepository;
private Map<String, Function<MyPojo, MyPojo>> designPattern;
#Autowired
public ControllerVersionTwo(ElasticRepository elasticRepository, MongoDbRepository mongoRepository, RedisRepository redisRepository) {
this.elasticRepository = elasticRepository;
this.mongoRepository = mongoRepository;
this.redisRepository = redisRepository;
// many more repositories
designPattern = new HashMap<>();
designPattern.put("elastic", myPojo -> elasticRepository.save(myPojo));
designPattern.put("mongo", myPojo -> mongoRepository.save(myPojo));
designPattern.put("redis", myPojo -> redisRepository.save(myPojo));
//many more put
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return designPattern.get(whereToSave).apply(myPojo).toString();
}
As you can see, I am leveraging a design pattern refactoring the if-else into a hashmap.
This post is not about if-else vs hashmap by the way.
Working fine, but please note, the map is a Map<String, Function<MyPojo, MyPojo>>, as I cannot construct a map of Map<String, #Repository>.
With this second version, the if-else is being refactored, but again, we need to hardcode the hashmap.
This is why I am having the idea to build a third version, where I can configure the map itself, via a spring boot property #Value for Map:
Here is what I tried:
#RestController
public class ControllerVersionThree {
#Value("#{${configuration.design.pattern.map}}")
Map<String, String> configurationDesignPatternMap;
private Map<String, Function<MyPojo, MyPojo>> designPatternStrategy;
public ControllerVersionThree() {
convertConfigurationDesignPatternMapToDesignPatternStrategy(configurationDesignPatternMap, designPatternStrategy);
}
private void convertConfigurationDesignPatternMapToDesignPatternStrategy(Map<String, String> configurationDesignPatternMap, Map<String, Function<MyPojo, MyPojo>> designPatternStrategy) {
// convert configurationDesignPatternMap
// {elastic:ElasticRepository, mongo:MongoDbRepository , redis:RedisRepository , ...}
// to a map where I can directly get the appropriate repository based on the key
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return designPatternStrategy.get(whereToSave).apply(myPojo).toString();
}
And I would configure in the property file:
configuration.design.pattern.map={elastic:ElasticRepository, mongo:MongoDbRepository , saveToRedis:RedisRepositry, redis:RedisRepository , ...}
And tomorrow, I would be able to configure add or remove the future repository target.
configuration.design.pattern.map={elastic:ElasticRepository, anotherElasticKeyForSameElasticRepository, redis:RedisRepository , postgre:PostGreRepository}
Unfortunately, I am stuck.
What is the correct code in order to leverage a configurable property for mapping a key with it's "which #Repository to use" please?
Thank you for your help.
You can create a base repository to be extended by all your repositories:
public interface BaseRepository {
MyPojo save(MyPojo onboarding);
}
so you will have a bunch of repositories like:
#Repository("repoA")
public interface ARepository extends JpaRepository<MyPojo, String>, BaseRepository {
}
#Repository("repoB")
public interface BRepository extends JpaRepository<MyPojo, String>, BaseRepository {
}
...
Those repositories will be provided by a factory:
public interface BaseRepositoryFactory {
BaseRepository getBaseRepository(String whereToSave);
}
that you must configure in a ServiceLocatorFactoryBean:
#Bean
public ServiceLocatorFactoryBean baseRepositoryBean() {
ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
serviceLocatorFactoryBean.setServiceLocatorInterface(BaseRepositoryFactory.class);
return serviceLocatorFactoryBean;
}
Now you can inject the factory wherever you need and get the repo want:
#Autowired
private BaseRepositoryFactory baseRepositoryFactory;
...
baseRepositoryFactory.getBaseRepository("repoA").save(myPojo);
...
Hope it helps.
Short answer:
create a shared interface
create multiple sub-class of this interface (one per storage) using different spring component names
Use a map to deal with aliases
use Spring context to retrieve the right bean by alias (instead of creating a custom factory)
Now adding a new storage is only adding a new Repository classes with a name
Explanation:
As mentioned in the other answer you first need to define a common interface as you can't use the CrudRepository.save(...).
In my example I reuse the same signature as the save method to avoid re-implementing it in the sub-classes of CrudRepository.
public interface MyInterface<T> {
<S extends T> S save(S entity);
}
Redis Repository:
#Repository("redis") // Here is the name of the redis repo
public class RedisRepository implements MyInterface<MyPojo> {
#Override
public <S extends MyPojo> S save(S entity) {
entity.setValue(entity.getValue() + " saved by redis");
return entity;
}
}
For the other CrudRepository no need to provide an implementation:
#Repository("elastic") // Here is the name of the elastic repo
public interface ElasticRepository extends CrudRepository<MyPojo, String>, MyInterface<MyPojo> {
}
Create a configuration for your aliases in application.yml
configuration:
design:
pattern:
map:
redis: redis
saveToRedisPlease: redis
elastic: elastic
Create a custom properties to retrieve the map:
#Component
#ConfigurationProperties(prefix = "configuration.design.pattern")
public class PatternProperties {
private Map<String, String> map;
public String getRepoName(String alias) {
return map.get(alias);
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
Now create the version three of your repository with the injection of SpringContext:
#RestController
public class ControllerVersionThree {
private final ApplicationContext context;
private PatternProperties designPatternMap;
public ControllerVersionThree(ApplicationContext context,
PatternProperties designPatternMap) {
this.context = context;
this.designPatternMap = designPatternMap;
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
String repoName = designPatternMap.getRepoName(whereToSave);
MyInterface<MyPojo> repo = context.getBean(repoName, MyInterface.class);
return repo.save(myPojo).toString();
}
}
You can check that this is working with a test:
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.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ControllerVersionThreeTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
void testSaveByRedis() {
// Given: here 'redis' is the name of the spring beans
HttpEntity<MyRequest> request = new HttpEntity<>(new MyRequest("redis", "aValue"));
// When
String response = restTemplate.postForObject("http://localhost:" + port + "/save", request, String.class);
// Then
assertEquals("MyPojo{value='aValue saved by redis'}", response);
}
#Test
void testSaveByRedisAlias() {
// Given: here 'saveToRedisPlease' is an alias name of the spring beans
HttpEntity<MyRequest> request = new HttpEntity<>(new MyRequest("saveToRedisPlease", "aValue"));
// When
String response = restTemplate.postForObject("http://localhost:" + port + "/save", request, String.class);
// Then
assertEquals("MyPojo{value='aValue saved by redis'}", response);
}
}
Have you tried creating a configuration class to create your repository map
#Configuration
public class MyConfiguration {
#Bean
public Map repositoryMap() {
Map<String, ? extends Repository> repositoryMap = new HashMap<>();
repositoryMap.put('redis', new RedisRepository());
repositoryMap.put('mongo', new MongoRepository());
repositoryMap.put('elastic', new ElasticRepository());
return Collections.unmodifiableMap(repositoryMap);
}
}
Then you could have the following in your rest controller
#RestController
#Configuration
public class ControllerVersionFour {
#Autowired
private Map<String, ? extends Repository> repositoryMap;
#PostMapping(path = "/save/{dbname}")
public String save(#RequestBody MyRequest myRequest, #PathVariable("dbname") String dbname) {
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return repisitoryMap.get(dbname).save(myPojo);
}
It might be better to have the db as a path/query parameter instead of having it in the request body. That way you may or may not be able to just save the request body depending on your use case instead of creating another pojo.
This post may also be useful for autowiring a map
I have a Java-Spring LIBRARY (NOT an application) which sends notifications via phone numbers. I'm using a Rest template to send a POST request. But instead of creating a new object of RestTemplate, I would want to use RestTemplateConfiguration #Configuration to do so.
IntelliJ version - 2020.2
Spring - 2.3.3.RELEASE
Java - 11
1st issue - When I'm trying to create a RestTemplateConfiguration class, I get the error -
Could not autowire. No beans of 'RestTemplateBuilder' type found.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
2nd issue If I create a new object of RestTemplateBuilder, it doesn't give the error, but this is not how I want this class to behave. Also, this doesn't seem to work when in the class (mentioned below) SamplePhoneNumbers.java, I try to use RestTemplate restTemplate. It is coming as null.
#Configuration
public class RestTemplateConfiguration {
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder;
return builder.build();
}
}
Here is the class which sends notifications and where I am trying to use the Rest Template.
#Slf4j
public class SamplePhoneNumbers implements SampleClassStratergy {
RestTemplate restTemplate;
private static SamplePhoneNumbers samplePhoneNumbers = null;
private String phoneNumbersNotificationServiceURL = Enums.NotificationURL.enum_value + Enums.PhoneNumberApi.enum_value;
SamplePhoneNumbers() {
this.restTemplate = new RestTemplate();
}
public static SamplePhoneNumbers getInstance() {
if(samplePhoneNumbers ==null) {
samplePhoneNumbers = new SamplePhoneNumbers();
}
return samplePhoneNumbers;
}
#Async
public void sendNotification(String title, String message, List<String> listOfPhoneNumbers) {
SmsMessage smsMessage= new SmsMessage(title, message, listOfPhoneNumbers, Collections.emptyList(), Collections.emptyList());
try {
HttpEntity<SmsMessage> newRequest = new HttpEntity<>(smsMessage);
restTemplate.postForObject(phoneNumbersNotificationServiceURL, newRequest, String.class);
log.info("Notification sent via phone number.");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
3rd issue: Is there any way I can get rid of getInstance() method so I don't have to handle Singleton logic? If I can create the class as a bean, that should work I guess, but I'm not sure how that can be achieved in this case.
Since this is a Library, it doesn't have a main method and no #SpringBootapplicationContext.
Can someone please assist me with the solution?
I also got the similar error while running Junit test case and resolved it by adding a bean of -
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder();
}
Do you want invoke your Java-Spring LIBRARY in a spring boot application? If so, you should add #ComponentScan in spring boot application main method as below.
pacakge com.prod
// for instance, Java-Spring LIRARY package is com.third
#SpringBootApplication
#ComponentScan(basePackages={"com.prod","com.third"})
public class Application {
Then you should do as below:
#Configuration
public class RestTemplateConfiguration {
#Bean(name="myRestTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
#Slf4j
#Component
public class SamplePhoneNumbers implements SampleClassStratergy {
#Autowired
#Qualifier("myRestTemplate")
RestTemplate restTemplate;
#Async
public void sendNotification(String title, String message, List<String> listOfPhoneNumbers) {
SmsMessage smsMessage= new SmsMessage(title, message, listOfPhoneNumbers, Collections.emptyList(), Collections.emptyList());
try {
HttpEntity<SmsMessage> newRequest = new HttpEntity<>(smsMessage);
restTemplate.postForObject(phoneNumbersNotificationServiceURL, newRequest, String.class);
log.info("Notification sent via phone number.");
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");
I have a implementation class which acts as a rabbitMQ sender class, I am trying to write unit test cases for this, but i am having doubts about mocking rabbitmq template.
This is my sender class code:
#Service
public class Car implements CarDelegate {
#Autowired
private RabbitTemplate rt;
#Value("${exchange}")
private String exchange;
#Value("${queue}")
private String queue;
#Override
public ResponseEntity<String> createCar(String model, String name) {
Car car = new Car();
car.setModel(Model);
car.setName(Name);
String jsonString;
jsonString = new ObjectMapper().writeValueAsString(car);
try {
rt.convertAndSend(exchange, queue, jsonString);
} catch (AmqpException e) {
//to implement
}
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}
}
My sender class is also my implementation method.
The test class for it is as below:
#RunWith(MockitoJUnitRunner.class)
public class CarTest {
private Car car;
#Mock
private RabbitTemplate rt;
#Test
public void create_valid() {
Car car = new Car(rt);
car.create("sedan", "arison");
String jsonString = "";
Mockito.doReturn("")
.when(rabbitTemplate.convertAndSend(null, null, jsonString))
.myMethod(Mockito.any(createLeadTest_valid.class));
Mockito.when(rabbitTemplate.convertAndSend(null, null, jsonString)).thenReturn("");
}
}
What is the correct way to mock rabbit template
For your specific case, no need to add behavior to your mock.
public class CarServiceTest {
#Test
public void create_valid() {
RabbitTemplate rt = Mockito.mock(RabbitTemplate.class);
CarService car = new CarService(rt);
ResponseEntity<String> response = car.create("sedan", "arison");
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
FYI, it is not good practice to manipulate ResponseEntity outside an HTTP adapter (typically a bean annotated with #Controller).
And RabbitTemplate#convertAndSend is supposed to provide a conversion mechanism, so you do not have to use Jackson directly.
Hoping this will help you !
I created an annotation for creating ThreadPoolTaskExecutors populated with values from the environment. However, when I autowire the bean it gives me a proxy and calling the methods on the proxy gives the wrong values.
If I manually access the target class, then I get the correct values.
Executor exec = (Executor) ((Advised) executor).getTargetSource().getTarget();
ThreadPoolTaskExecutor taskExec = (ThreadPoolTaskExecutor) exec;
I have been scratching my head for a while now as to why I'm getting a proxy bean, but can't seem to figure it out.
I am using an annotation to import my registrar class that implements ImportBeanDefinitionRegistrar to register the bean. The registrar code is below:
public class ExecutorEnumerationRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
public static final String CORE_POOL_SIZE = "corePoolSize";
public static final String MAX_POOL_SIZE = "maxPoolSize";
public static final String QUEUE_CAPACITY = "queueCapacity";
public static final String THREAD_NAME_PREFIX = "threadNamePrefix";
private static final String REJECTED_EXECUTION_HANDLER = "rejectedExecutionHandler";
private static final String NAMES = "names";
private static final String REJECTED_HANDLER = "rejectedHandler";
private Environment env;
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<String, Object> attrs = importingClassMetadata.getAnnotationAttributes(ThreadPoolTaskExecutorCreator.class.getName(), true);
final String[] beanNames = (String[]) attrs.get(NAMES);
final String[] policyClass = (String[]) attrs.get(REJECTED_HANDLER);
for (int x = 0; x < beanNames.length; x++) {
createAndRegisterBean(beanNames[x], policyClass[x], registry);
}
}
private void createAndRegisterBean(String name, String policyClass, BeanDefinitionRegistry registry) {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(ThreadPoolTaskExecutor.class);
bd.setAutowireCandidate(true);
bd.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
MutablePropertyValues mpv = bd.getPropertyValues();
populateProperties(mpv, name, policyClass);
registry.registerBeanDefinition(name, bd);
}
private void populateProperties(MutablePropertyValues mpv, String name, String policyClass) {
mpv.add(CORE_POOL_SIZE, Integer.valueOf(env.getProperty(name + "." + CORE_POOL_SIZE)));
mpv.add(MAX_POOL_SIZE, Integer.valueOf(env.getProperty(name + "." + MAX_POOL_SIZE)));
mpv.add(QUEUE_CAPACITY, Integer.valueOf(env.getProperty(name + "." + QUEUE_CAPACITY)));
try {
mpv.add(REJECTED_EXECUTION_HANDLER, Class.forName(policyClass).newInstance());
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
mpv.add(THREAD_NAME_PREFIX, name + "-");
}
#Override
public void setEnvironment(Environment environment) {
env = environment;
}
}
Annotation to import the registrar:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Documented
#Import(ExecutorEnumerationRegistrar.class)
public #interface ThreadPoolTaskExecutorCreator{
String[] names();
String[] rejectedHandler() default ThreadPoolPolicyHandlers.CALLER_RUNS_POLICY;
}
I have tested with the following code:
Spring Boot Class:
#EnableDiscoveryClient
#ComponentScan("my.test.classes")
#ThreadPoolTaskExecutorCreator(names = {"testExecutor"}, rejectedHandler = ThreadPoolPolicyHandlers.DISCARD_POLICY)
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
SessionAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
JndiDataSourceAutoConfiguration.class,
JndiConnectionFactoryAutoConfiguration.class,
RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class})
public class TestBoot {
public static void main(String[] args) {
SpringApplication.run(TestBoot.class, args);
}
}
All versions from spring-boot-starter-parent 1.4.5.RELEASE
I wrote a JUnit test that checks the values and it passes. The only time it doesn't work is when I autowire it in a Spring Boot eureka application. Is there anything I can do so that it doesn't autowire a proxy bean? I have searched through the documentation and looked at all the related classes, but I don't see anything related to why it's a proxy. Also, why does it give incorrect values when accessed through the proxy?
Seems you are missing the code for registering the instance of your ImportBeanDefinitionRegistrar ( in your example that is ExecutorEnumerationRegistrar )
So there are two ways to register the ImportBeanDefinitionRegistrar use the #Import annotation directly or implement the ImportSelector interface which can give you more generic configuration options.
For your case simply adding the #Import({ExecutorEnumerationRegistrar.class}) on the Configuration class will do the trick.
#EnableDiscoveryClient
#ComponentScan("my.test.classes")
#ThreadPoolTaskExecutorCreator(names = {"testExecutor"}, rejectedHandler = ThreadPoolPolicyHandlers.DISCARD_POLICY)
// THIS IS REQUIRED
#Import({ExecutorEnumerationRegistrar.class})
// See Above
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
SessionAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
JndiDataSourceAutoConfiguration.class,
JndiConnectionFactoryAutoConfiguration.class,
RedisAutoConfiguration.class, RedisRepositoriesAutoConfiguration.class})
public class TestBoot {
public static void main(String[] args) {
SpringApplication.run(TestBoot.class, args);
}
}
and just remember to use the #Qualifier when autowiring the instance of ThreadPoolTaskExecutor. See example component
#Component
public class Component {
#Autowired
#Qualifier("testExecutor")
private ThreadPoolTaskExecutor exec;
// you methods
}