Mockito.When not working for custom RestTemplate - java

I need to do testing for the following service. When I try to do Mockito.When for custom restTemplate I get the error "org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);".
However, when I don't do when(env.getProperty("test1")).thenReturn("test"); I get the error "I/O error on POST request for "/null/Test/null": null;"
How to make test properly for such service ?
Test class
#InjectMocks
private AccountService _accountService;
#Mock
private RestTemplate _restTemplate;
#Test
public void shouldGetAccount_200() {
//when(_accountService.getRestTemplate()).thenReturn(_restTemplate);
when(env.getProperty("test1")).thenReturn("test");
when(env.getProperty("test2")).thenReturn("2");
when(_restTemplate.exchange(eq(String.format("%s/Test/%s", env.getProperty("test1"), env.getProperty("test2")), _accId), eq(HttpMethod.GET), any(), eq(TestRequest.class)))
.thenReturn(new ResponseEntity<>(_resp, HttpStatus.OK));
assertThat(_accountService.getAcc(_accId, "test", "test", "test")).isEqualTo(_resp);
}
Service
public IResponse getAcc(String accId, String sessionId, String apiKey, String userName) {
RestTemplate restTemplate = getRestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.COOKIE, sessionId);
headers.add("X-Forwarded-For", getIPAddress());
headers.add("Authorization", String.format("PS-Auth key=%s; runas=%s;", apiKey, userName));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(null, headers);
ResponseEntity<TestRequest> response;
return restTemplate.exchange(String.format("%s/Test/%s", env.getProperty("test1"), env.getProperty("test2"))), accId),HttpMethod.GET, entity, TestRequest.class);
}
public RestTemplate getRestTemplate() {
SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault());
CloseableHttpClient httpClient = HttpClients.custom().setRoutePlanner(routePlanner)
.setConnectionManager(poolingHttpClientConnectionManager).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<ClientHttpRequestInterceptor> interceptorList = restTemplate.getInterceptors();
if (tracingClientHttpRequestInterceptor != null) {
interceptorList.add(tracingClientHttpRequestInterceptor);
interceptorList.add(traceLogClientHttpRequestInterceptor);
restTemplate.setInterceptors(interceptorList);
}
return restTemplate;
}

Using when() you assign the behavior to the mock that Mockito creates for you. This mock, however, never gets used since your getAcc() method calls the getRestTemplate that always creates a new instance of RestTemplate.
The obvious solution here is to use the put the getRestTemplate method in your configuration class and use it as a bean factory, autowiring* the RestTemplate bean into your AccountService. Then your #InjectMocks annotation will work as expected and you'll see that the service under test does indeed use the provided mock object.
*remember, there's three ways you can inject a bean in Spring - using field injection, method injection or constructor. That's not difficult to look up online so, I'll just say that most use-cases are handled best using constructor injection:
#Autowired
public AccountService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
Secondly, you don't show this but I'm pretty sure that env variable in getAcc is some kind of environment variables holder and is also not injected into the service properly so it doesn't get mocked. I would generally recommend to stick to #ConfigurationProperties (and you could easily mock those), or at least look into MockEnvironment.
As an aside, I can't think of why a service should need a new RestTemplate instance every time, and creating the RestTemplate is definitely beyound the scope of AccountService, so moving that to configuration also improves your code's structure. As a bonus, you can reuse the same RestTemplate bean in other services should you need that.
Evetually you might want to add a layer of abstraction making a separate bean to take care of constructing the url and adding the necessary headers to the RestTemplate request, so that the getAcc method gets more readable.

Related

Spring Boot Test : Mix up #MockBean and #SpyBean in same Test class on same Bean object?

Here a test code sample with Spring Boot Test:
#SpringBootTest
#AutoConfigureMockMvc
class SendMoneyControllerSpringBootSpyBeanTest {
#Autowired
private MockMvc mockMvc;
#SpyBean
private SendMoneyUseCase sendMoneyUseCase;
#Test
void testSendMoneyWithMock() {
...
}
#Test
void testSendMoneyWithSpy() {
...
}
}
Now suppose the two test methods like in the snippet above. One is using the spy version of the spring bean, whereas the other is using a mock version of the spring bean. How can I mix up both and distinguish them in my test methods ?
For example, can I do :
#SpringBootTest
#AutoConfigureMockMvc
class SendMoneyControllerSpringBootSpyBeanTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private SendMoneyUseCase sendMoneyUseCaseMocked;
#SpyBean
private SendMoneyUseCase sendMoneyUseCaseSpied;
}
I know that in spring bean container, there is only one of sendMoneyUseCaseMocked or sendMoneyUseCaseSpied because they are the same java type. I can use a qualifier, to refer them by name. But in that case, how do I write my mock condition in the corresponding test method (write either mock condition on the mocked bean or the spied condition on the spied bean in the concerned test method).
EDIT : Another approach is to remove the line of code #MockBean, like this, the spied method is working. Consequently, I need then to programmatically code #MockBean in the mocked test method with Spring boot API, but how to do ?.
Thx.
I see two solutions.
Either use two test classes. One with the #MockBean object and one with the #SpyBean object. Both can inherit from the same abstract parent-class.
Let Spring Boot inject #MockBean and then replace it manually in the testSendMoneyWithSpy() test with the #SpyBean object using reflection.
But, why do you want to use the spy-object at all?
If you make unit-tests, you should mock the service and only test the controller here. And test the SendMoneyUseCase class in the SendMondeyUseCaseTest test.
(btw. strange name SendMondeyUseCase why not SendMoneyService or SendMoneyComponent. Or MoneySendService?)
Here the solution, first the solution code (my real test case, the one in first topic is more like a study code) :
#Slf4j
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class DocumentsApiTest extends ProductAPITestRoot {
#Autowired
private TestRestTemplate restTemplate;
#SpyBean
private AlfrescoService alfrescoServiceSpy;
#MockBean
private CloseableHttpClient closeableHttpClient;
#Test
public void testUploadDocument_SUCCESS() {
HttpHeaders headers = createOKHttpHeaders();
DocumentWithFile documentWithFile = createDocumentWithFile();
doReturn(StringUtils.EMPTY).when(alfrescoServiceSpy).sendDocument(anyString(), any(InputStream.class), eq(documentWithFile.getNomFichier()));
HttpEntity<DocumentWithFile> request = new HttpEntity<>(documentWithFile, headers);
ResponseEntity<Long> response = restTemplate.exchange(
"/documents/upload", HttpMethod.POST, request, Long.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isEqualTo(1L);
}
#Test
public void testUploadDocument_500_INTERNAL_SERVER_ERROR() {
HttpHeaders headers = createOKHttpHeaders();
DocumentWithFile documentWithFile = createDocumentWithFile();
doThrow(new AlfrescoServiceException("Alfresco has failed !")).when(alfrescoServiceSpy).sendDocument(anyString(), any(InputStream.class), eq(documentWithFile.getNomFichier()));
HttpEntity<DocumentWithFile> request = new HttpEntity<>(documentWithFile, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/documents/upload", HttpMethod.POST, request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
assertThat(response.getBody()).contains("Internal Server Error");
}
#Test
public void testUploadDocument_502_BAD_GATEWAY() throws IOException {
HttpHeaders headers = createOKHttpHeaders();
DocumentWithFile documentWithFile = createDocumentWithFile();
CloseableHttpResponse alfrescoResponse = mock(CloseableHttpResponse.class);
when(closeableHttpClient.execute(any(HttpPost.class))).thenReturn(alfrescoResponse);
when(alfrescoResponse.getStatusLine()).thenReturn(new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.BAD_GATEWAY.value(), "FINE!"));
HttpEntity<DocumentWithFile> request = new HttpEntity<>(documentWithFile, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/documents/upload", HttpMethod.POST, request, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_GATEWAY);
assertThat(response.getBody()).contains("Erreur Alfresco");
}
private static HttpHeaders createOKHttpHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + OK_TOKEN);
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
private static DocumentWithFile createDocumentWithFile() {
String fileAsString = RandomStringUtils.randomAlphabetic((int) 1e6);
byte[] fileAsStringBase64 = Base64.getEncoder().encode(fileAsString.getBytes());
DocumentWithFile documentWithFile = new DocumentWithFile();
String nomFichierExpected = "nomFichier.pdf";
documentWithFile
.id(8L)
.idCatalogue("idCatalogue")
.nom("nom")
.reference("reference")
.version("version")
.type(TypeDocument.IPID)
.fichier(new String(fileAsStringBase64))
.nomFichier(nomFichierExpected);
return documentWithFile;
}
}
If you look carrefully, the right way to mixup both : a mock and a spy of the same bean.
You can use #MockBean....and you use the spy version of this #MockBean with when(...).thenCallRealMethod(). But the REAL drawback of this is that if the #MockBean bean contains #Value field injection, thus they are NOT initialized. Meaning that #MockBean annotation set #Value fields of the mocked bean to null.
So I went for solution 2) because I need injection of #Value fileds.
Instead of #MockBean use #SpyBean of the concerned spring bean. Like this, you've got now the real bean. The question is how do I use it like a #MockBean. So to use a #SpyBean like a #MockBean, you needs to force the returned value of your #SpyBean bean like this for example :
doReturn(StringUtils.EMPTY).when(alfrescoServiceSpy).sendDocument(anyString(), any(InputStream.class), eq(documentWithFile.getNomFichier()));
As you can see, although alfrescoServiceSpy call the real code (not a mock), then you still can change its default behavior (calling the real code) with the mocked behavior like the snipped code above as example.
So test methods that need the mock version of #SpyBean declare an instruction of mocked behavior to do. And test methods that needs real code they don't do anything and #Value will be injected into the bean annotated #SpyBean the right way.
I'am personnaly satisfied of this code version.
Thanks you very much all.

external URL with API endpoint in SpringBoot [duplicate]

I want to call another web-api from my backend on a specific request of user. For example, I want to call Google FCM send message api to send a message to a specific user on an event.
Does Retrofit have any method to achieve this? If not, how I can do that?
This website has some nice examples for using spring's RestTemplate.
Here is a code example of how it can work to get a simple object:
private static void getEmployees()
{
final String uri = "http://localhost:8080/springrestexample/employees.xml";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
Modern Spring 5+ answer using WebClient instead of RestTemplate.
Configure WebClient for a specific web-service or resource as a bean (additional properties can be configured).
#Bean
public WebClient localApiClient() {
return WebClient.create("http://localhost:8080/api/v3");
}
Inject and use the bean from your service(s).
#Service
public class UserService {
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(3);
private final WebClient localApiClient;
#Autowired
public UserService(WebClient localApiClient) {
this.localApiClient = localApiClient;
}
public User getUser(long id) {
return localApiClient
.get()
.uri("/users/" + id)
.retrieve()
.bodyToMono(User.class)
.block(REQUEST_TIMEOUT);
}
}
Instead of String you are trying to get custom POJO object details as output by calling another API/URI, try the this solution. I hope it will be clear and helpful for how to use RestTemplate also,
In Spring Boot, first we need to create Bean for RestTemplate under the #Configuration annotated class. You can even write a separate class and annotate with #Configuration like below.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Then, you have to define RestTemplate with #Autowired or #Injected under your service/Controller, whereever you are trying to use RestTemplate. Use the below code,
#Autowired
private RestTemplate restTemplate;
Now, will see the part of how to call another api from my application using above created RestTemplate. For this we can use multiple methods like execute(), getForEntity(), getForObject() and etc. Here I am placing the code with example of execute(). I have even tried other two, I faced problem of converting returned LinkedHashMap into expected POJO object. The below, execute() method solved my problem.
ResponseEntity<List<POJO>> responseEntity = restTemplate.exchange(
URL,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<POJO>>() {
});
List<POJO> pojoObjList = responseEntity.getBody();
Happy Coding :)
Create Bean for Rest Template to auto wiring the Rest Template object.
#SpringBootApplication
public class ChatAppApplication {
#Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ChatAppApplication.class, args);
}
}
Consume the GET/POST API by using RestTemplate - exchange() method. Below is for the post api which is defined in the controller.
#RequestMapping(value = "/postdata",method = RequestMethod.POST)
public String PostData(){
return "{\n" +
" \"value\":\"4\",\n" +
" \"name\":\"David\"\n" +
"}";
}
#RequestMapping(value = "/post")
public String getPostResponse(){
HttpHeaders headers=new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity=new HttpEntity<String>(headers);
return restTemplate.exchange("http://localhost:8080/postdata",HttpMethod.POST,entity,String.class).getBody();
}
Refer this tutorial[1]
[1] https://www.tutorialspoint.com/spring_boot/spring_boot_rest_template.htm
As has been mentioned in the various answers here, WebClient is now the recommended route.
You can start by configuring a WebClient builder:
#Bean
public WebClient.Builder getWebClientBuilder(){
return WebClient.builder();
}
Then inject the bean and you can consume an API as follows:
#Autowired
private WebClient.Builder webClientBuilder;
Product product = webClientBuilder.build()
.get()
.uri("http://localhost:8080/api/products")
.retrieve()
.bodyToMono(Product.class)
.block();
Does Retrofit have any method to achieve this? If not, how I can do that?
YES
Retrofit is type-safe REST client for Android and Java. Retrofit turns your HTTP API into a Java interface.
For more information refer the following link
https://howtodoinjava.com/retrofit2/retrofit2-beginner-tutorial
In this case need download whit my API, files hosted in other server.
In my case, don't need use a HTTP client to download the file in a external URL, I combined several answers and methods worked in previous code for files that were in my local server.
My code is:
#GetMapping(value = "/download/file/pdf/", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<Resource> downloadFilePdf() throws IOException {
String url = "http://www.orimi.com/pdf-test.pdf";
RestTemplate restTemplate = new RestTemplate();
byte[] byteContent = restTemplate.getForObject(url, String.class).getBytes(StandardCharsets.ISO_8859_1);
InputStream resourceInputStream = new ByteArrayInputStream(byteContent);
return ResponseEntity.ok()
.header("Content-disposition", "attachment; filename=" + "pdf-with-my-API_pdf-test.pdf")
.contentType(MediaType.parseMediaType("application/pdf;"))
.contentLength(byteContent.length)
.body(new InputStreamResource(resourceInputStream));
}
and it works with HTTP and HTTPS urls!
Since the question explicitly tags spring-boot, it worth noting that recent versions already ship a pre-configured instance of a builder for WebClient, thus you can directly inject it inside your service constructor without the needing to define a custom bean.
#Service
public class ClientService {
private final WebClient webClient;
public ClientService(WebClient.Builder webClientBuilder) {
webClient = webClientBuilder
.baseUrl("https://your.api.com")
}
//Add all the API call methods you need leveraging webClient instance
}
https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/boot-features-webclient.html
Simplest way I have found is to:
Create an annotated interface (or have it generated from somehing like OpenAPI)
Give that interface to Spring RestTemplate Client
The Spring RestTemplate Client will parse the annotations on the interface and give you a type safe client, a proxy-instance. Any invocation on the methods will be seamlessly translated to rest-calls.
final MyApiInterface myClient = SpringRestTemplateClientBuilder
.create(MyApiInterface.class)
.setUrl(this.getMockUrl())
.setRestTemplate(restTemplate) // Optional
.setHeader("header-name", "the value") // Optional
.setHeaders(HttpHeaders) // Optional
.build();
And a rest call is made by inoking methods, like:
final ResponseEntity<MyDTO> response = myClient.getMyDto();

how to junit test on restTemplate?

I have a problem to mock restTemplate with Mockito
code want to be test:
public class Feature{
public static String getfeature(String url){
RestTemplate restTemplate = new RestTemplate();
String xml = "\"feature\": 1";
String json = restTemplate.postForObject(url, xml, String.class);
return json;
}
}
junit code:
#Mock
RestTemplate restTemplate=mock(RestTemplate.class);
#Test
public void testGetfeature(){
string testResponse= "\"feature\": 1";
Mockito.when((String)restTemplate.postForObject(
Mockito.any(String.class),
Mockito.any(Map.class),
Mockito.any(Class.class)
)).thenReturn(testResponse);
Feature feature = new Feature();
feature.getfeature("http://mockValue");
}
I set breaking point at feature.getfeature("http://mockValue"). It still try to connect to the remote server. I don't want postForObject to connect to the http://mockValue.
How should I mock the restTemplate to make postForObject not to connect to http://mockValue?
You are creating a new RestTemplate object in getfeature() method. So, mocking RestTemplate has no effect. Either take RestTemplate as an argument in getfeature() method or take it as constructor argument in Feature class.
Then from the test class, you can mock RestTemplate and pass it like below:
Feature feature= new Feature(mockRestTemplate);
feature.getfeature(url);
Or
Feature feature = new Feature();
feature.getfeature(mockRestTemplate, url);
You have to make the necessary changes in the Feature class based on the decision.
Here is the running code sample:
Main class:
public class Feature {
public static String getFeature(String url, RestTemplate restTemplate) {
return restTemplate.postForObject(url, "", String.class);
}
}
Test class:
Notice the way the RestTemplate is mocked and then the response is mocked.
public class FeatureTest {
#Test
public void testFeature() {
RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(restTemplate.postForObject(Mockito.any(String.class),
Mockito.any(Object.class), Mockito.any(Class.class))).thenReturn("abc");
System.out.println(Feature.getFeature("http://abc", restTemplate));
}
}
The running code sample is also available at github
Feature.java and FeatureTest.java
There is no need to constantly create a RestTemplate object.
You can create one and inject it into the class.
Then, in your unit test, create a mock RestTemplate and inject that.
Edit:
Concerning the static method.
Don't use a static method,
that is terrible.
If you are using Spring (or any other Dependency Injection framework)
just inject an instance of Feature where ever you want to make the rest call.
Also,
in the Spring world Feature is a service class.
Use the #Service annotation and make the method not static.
I think you need to change your code to make your unit test work atleast using Mockito or you have to use some other library like powermock to mock local object instantiation.
1) create a constructor that accepts RestTemplate as argument to inject your mock.
or
2) create some setter method to inject that RestTemplate.
one more way is to create another method where RestTemplate can be passed.
public String getStringAsJson(RestTemplate restTemplate, String url, String xml) {
return restTemplate.postForObject(url, xml, String.class);
}
then in your test:
RestTemplate mockRestTemplate = mock(RestTemplate.class);
when(restTemplate.postForObject(mockurl, mockUrl, mockXml)).thenReturn("Json");
feature.getStringAsJson(mockRestTemplate,mockUrl,mockXml);
how to junit test on restTemplate?
We test what it returns.
Currently your implementation does nothing itself, it simply delegates to a RestTemplate and return its result.
The fiveelements answer describes the implementation (with broad accepted values as parameters) :
#Test
public void testFeature() {
RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(restTemplate.postForObject(Mockito.any(String.class),
Mockito.any(Object.class), Mockito.any(Class.class))).thenReturn("abc");
System.out.println(Feature.getFeature("http://abc", restTemplate));
}
That doesn't assert the actual behavior : the URL could be erroneous, the body posted or the response could be wrong and so for... and this test will never detect that. Finally, very important things could be wrong in this implementation and we could not detect that. The value of this kind of test is so weak.
Since this method doesn't perform itself logic, this is better suited for an integration test that could assert and catch much more thing/issues in the actual behavior :
#Test
public void testFeature() {
String actualFeature = Feature.getFeature("http://...");
String expectedFeature = ...;
Assertions.assertEquals(expectedFeature, actualFeature);
}
If you use PowerMockito then you can do like following:
No code change needed here for Feature class then you can use this directly if PowerMockito lib available in prj
RestTemplate mockedRestTemplate = PowerMockito.mock(RestTemplate.class);
PowerMockito.whenNew(RestTemplate.class).withAnyArguments().thenReturn(mockedRestTemplate);

Test RestTemplate Interceptors

i've implemented a Interceptors for RestTemplate (RestTemplateInterceptor.class):
#Autowired
private RestConfigurations configurations;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.additionalInterceptors((ClientHttpRequestInterceptor) (httpRequest, bytes, clientHttpRequestExecution) -> {
HttpHeaders headers = httpRequest.getHeaders();
headers.add("X-API-KEY", configurations.getApiKey());
return clientHttpRequestExecution.execute(httpRequest,bytes);
}).build();
}
Now I want to test it. :)
One try looks like that:
#ExtendWith(SpringExtension.class)
#RestClientTest({RestTemplateInterceptor.class, RestConfigurations.class})
#AutoConfigureMockMvc
#SpringBootTest
class MyTestClass {
public static final String URL = "http://testMe.com/";
#Autowired
MockRestServiceServer mockServer;
#Autowired
RestTemplateBuilder restTemplateBuilder;
#Test
#DisplayName("Should include header X-API-KEY")
void headerShouldContainsXApiKey() throws Exception {
mockServer.expect(requestTo(URL)).andRespond(withSuccess("Hello world", TEXT_PLAIN));
String body = restTemplateBuilder.build().exchange(URL//
, GET, //
null, //
String.class).getBody();
assertThat(body).isEqualTo("Hello world");
mockServer.expect(header("X-API-KEY", ""));
}
But i failed with:
java.lang.AssertionError: No further requests expected: HTTP GET http://test.hornbach.de/
0 request(s) executed.
Anyone a hint? Thanks
If you check the Javadoc, you'll see that when you call additionalInterceptors, you're not modifying the existing builder instance but instead getting a new builder with a slightly different configuration. When you then call restTemplateBuilder.build() in your test case, you're building a template that has the unmodified configuration.
The ideal way to test something like this is to do so directly on the interceptor instance itself, but if you're wanting to perform a functional test by calling through to a mock server, you should inject the completed RestTemplate instance into your test case.

Service injected into spring controller is not available in one of the functions

I am writing spring controller, which injects a bean.
The bean is added in config(we use java config for everything):
#Bean
public NotificationService notificationService() {
return new NotificationService();
}
The service itself has few injected dependencies and few functions:
public class NotificationService {
#Inject
NotificationRepository notificationRepository;
#Inject
ProjectRepository projectRepository;
#Inject
ModelMapper modelMapper;
public NotificationDto create(NotificationDto notificationDto) {
//convert to domain object, save, return dto with updated ID
return notificationDto;
}
public void markAsRead(Long id, String recipientNip) {
//find notification, update status
}
}
Model mapper has almost no configuration, is only set to strict. Meanwhile repositoriers are interfaces extending JpaRepository with no custom functions. They are found by #EnableJpaRepositories.
Finally I have controller that tries to use the code above:
#RestController
#RequestMapping("/notifications")
public class NotificationController extends ExceptionHandlerController {
#Autowired
private NotificationService notificationService;
#PreAuthorize("isFullyAuthenticated() and hasRole('create_notification')")
#RequestMapping(method = RequestMethod.POST, consumes = MediaTypeExtension.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> createNotification(#Valid #RequestBody(required = true) final NotificationDto notification) {
this.notificationService.create(notification);
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
#PreAuthorize("isFullyAuthenticated() and hasRole('update_notification')")
#RequestMapping(value = "/{id}/read", method = RequestMethod.PUT)
private ResponseEntity<?> markNotificationAsRead(#PathVariable("id") Long id, #AuthenticatedContractor ContractorDto contractor) {
this.notificationService.markAsRead(id, contractor.getNip());
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.OK);
}
}
All controllers are added trough #ComponentScan, based on their package.
As you can see both functions use notificationService. When I send POST for create on /notifications the notificationService is properly injected. In the same controller, when I do PUT request on /{id}/read, the notificationService is null.
I think it has something to do with spring putting things into its container, and for some reason not being able to do it for that one function. I have few more functions in the controller and in all of them notificationService is properly injected. I don't see any real difference between createNotification and markNotificationAsRead functions and I couldn't find anything even remotely related on google/stack. In all cases the service wouldn't inject at all because of configuration mistake.
Edit
I have tried changing things around in the function until it has started working. My final code looks like this:
#PreAuthorize("isFullyAuthenticated() and hasRole('update_notification')")
#RequestMapping(value = "{id}/read", method = RequestMethod.PUT)
public ResponseEntity<?> read(#PathVariable("id") Long id, #AuthenticatedContractor ContractorDto contractor) {
this.notificationService.markAsRead(id, contractor.getNip());
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.OK);
}
and it works. Honestly I can't see any difference from my original code, and I have been staring at it for last hour or so. The imports are the same too.
I have also noticed(on unworking code) that while all functions from the controller on debug stack were marked as
NotificationController.functionName(arguments) line: x
The non working function was:
NotificationController$$EnhancerBySpringCGLIB$$64d88bfe(NotificationController).‌​markNotificationAsRead(ContractorDto) line: 86
Why this single function was enhanced by spring CGLIB I have no idea. I have tried looking it up, but for now I came empty handed. Even though the code started to work I am leaving the question open in order to find the underlying cause.
Your method markNotificationAsRead is private and that probably causes the issue. I've just had same issue with final method - this message appeared in log:
2016-11-28 17:19:14.186 INFO 97079 --- [ main] o.s.aop.framework.CglibAopProxy : Unable to proxy method [public final java.util.Map com.package.controller.MyController.someMethod(javax.servlet.http.HttpServletResponse)] because it is final: All calls to this method via a proxy will NOT be routed to the target instance.
Looks like in one case we see a CGLib proxy, and in another - the actual class. Only one of those has all the fields injected, looks like the proxy has all fields nulls. But it doesn't matter - the point is your method should be public and not final in order to be proxied properly by #PreAuthorize methods.
I was also facing the same issue. It was all due to the private access modifier used and #PreAuthorize. Making the controller method private does not make an issue if you do not make it secure. But, to make secure, make it public.

Categories

Resources