I have two controllers (UserController and ClientController), both of the controllers are located on the same package (com.myapp.controllers.identity), and my main application file located on the parent package (com.myapp).
I create the ClientController first and it works fine. Later on, I create the UserController. When I called the UserController, it always returns 404.
Here is the snippet of my controllers' files and main application file
Application.java
package com.myapp;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
ClientController.java
package com.myapp.controllers.identity;
#RestController
#RequestMapping(value = "/api/identity")
#Validated
public class ClientController {
#GetMapping(value = "/clients/{clientId}")
public ResponseEntity<?> getClientByClientId(#PathVariable("clientId") String clientId) {
}
}
UserController.java
package com.myapp.controllers.identity;
#RestController(value = "UserController")
#RequestMapping(value = "/api/identity")
#Validated
public class UserController {
public static final Logger logger = LoggerFactory.getLogger(UserController.class.getName());
#Autowired
private UserService userService;
#GetMapping(value = "/users/client/:clientId")
public ResponseEntity<?> getAllUsersByClientId(#PathVariable String clientId)
{
}
}
Can anybody help me solve it?
There is one simple problem in your userControler, and that is #GetMapping(value = "/users/client/:clientId") your format of capturing parameter
this kind of parameter is not supported in spring as #chrylis -on strike- mentioned
package com.myapp.controllers.identity;
#RestController(value = "UserController")
#RequestMapping(value = "/api/identity")
#Validated
public class UserController {
public static final Logger logger = LoggerFactory.getLogger(UserController.class.getName());
#Autowired
private UserService userService;
#GetMapping(value = "/{clientId}")
public ResponseEntity<?> getAllUsersByClientId(#PathVariable String clientId)
{
}
}
Related
I am trying to write integration test in Spring Boot 2.7.3.
This application should scrape data from some external REST service. To call rest service I am using RestTemplate from spring framework
#Service
public class UserService {
private final RestTemplate restTemplate;
private String url = "http://localhost:3000/user/{name}";
#Autowired
public UserService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public UserData getUserInfo(UserInfo userInfo) {
return restTemplate.getForObject(url, UserData.class, userInfo.getName());
}
}
This service is used by rest controller that is accepting name as a path parameter (we can call it localhost:8080/info?name=joe)
#RestController
public class UserController {
private UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("/info")
#ResponseBody
public UserData getUserData(UserInfo userInfo) {
return userService.getUserInfo(userInfo);
}
}
I have created controller annotated with #RestControllerAdvice and handling method that should be invoked when throwing HttpClientErrorException
#RestControllerAdvice
public class UserExceptionHandler {
#Autowired
public UserExceptionHandler(){
}
#ExceptionHandler(value = {HttpClientErrorException.class})
public ErrorResponse clientErrorHandle(HttpClientErrorException e) {
return ErrorResponse.builder().error("could not retrieve user info").build();
}
}
Problem is that below test is not hitting UserExceptionHandler
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerTest {
#Autowired
RestTemplate restTemplate;
#Autowired
UserController userController;
private MockRestServiceServer mockServer;
#BeforeEach
public void setUp() {
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testGetRootResourceOnce() {
mockServer.expect(once(), requestTo("http://localhost:3000/user/joe"))
.andRespond(withBadRequest().contentType(MediaType.APPLICATION_JSON).body("{\"error\": \"Error while getting data\"}"));
UserInfo userInfo = UserInfo.builder().name("joe").build();
UserData userData = userController.getUserData(userInfo);
mockServer.verify();
assertThat(userData).isNotNull();
}
}
While debugging I set breakpoint on UserExceptionHandler constructor so I assume that it has been created.
But why method clientErrorHandle was not invoked?
I see that RestTemplate throw HttpClientErrorException (please see picture below) so it should be intercepted by my handler, but it was not
I have a simple Rest Controller as below
#RestController
public class HealthController {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
private HealthService healthService;
#Autowired
public HealthController(HealthService healthService) {
this.healthService = healthService;
}
#RequestMapping(value = "/health", method = RequestMethod.GET)
public ResponseEntity<?> healthCheck() {
return healthService.checkHealth();
}
}
The service class is below
#Service
public class HealthService {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
public ResponseEntity<?> checkHealth() {
logger.info("Inside Health");
if (validateHealth()) {
return new ResponseEntity<>("Healthy", HttpStatus.OK);
} else {
return new ResponseEntity<>("Un Healthy", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
boolean validateHealth() {
return true;
}
}
The corresponding unit test for the controller class as below
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HealthController.class)
public class HealthControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HealthService healthService;
#Test
public void checkHealthReturn200WhenHealthy() throws Exception {
ResponseEntity mockSuccessResponse = new ResponseEntity("Healthy", HttpStatus.OK);
when(healthService.checkHealth()).thenReturn(mockSuccessResponse);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/health").accept(
MediaType.APPLICATION_JSON);
MvcResult healthCheckResult = mockMvc
.perform(requestBuilder).andReturn();
Assert.assertEquals(HttpStatus.OK.value(), healthCheckResult.getResponse().getStatus());
}
}
The problem I have is my CustomLogger. Since it has external dependencies am having issues in trying to test this.The same kind of logger is present in my service classes too.
How can I test such a class. I tried the below stuffs
Created a custom class name CustomLoggerForTest under test. Used
ReflectionTestUtils.setField(healthService, "logger", new CustomerLoggerForTest(HealthService.class.getName()));
in the setUp. But it did not help. Using this we cannot set the static fields hence tried even converting them to be non-static
Tried with mocking the CustomLogger in setup as below
mockStatic(CustomLogger.class); when(CustomLogger.getLogger(any())) .thenReturn(new CustomLoggerForTest(HealthController.class.getName()));
But no luck.
Is there anything that am doing wrong that is causing this?
When I call another Service (User-service) from one service (API-gateway) Using Feign Client, I'm getting an Error
There are two services
User-service
API-gateway
In my API-gateway
FeignClient
#FeignClient(contextId = "user-by-email",name = "user-service")
#Service
public interface UserByEmail {
#RequestMapping(value = "/email/{email}", consumes= MediaType.APPLICATION_JSON_VALUE)
User findByEmail(#PathVariable("email") String email);
}
Controller
#RequestMapping("/test")
public class TestController {
#Autowired
private UserByEmail userByEmail;
#GetMapping(value = "/{email:.+}")
public ResponseEntity testUser(#PathVariable("email") String username) {
return ResponseEntity.ok(userByEmail.findByEmail(username));
}
}
I need to call the following (User-service)
Controller
#EnableFeignClients
#RestController
public class UserController extends BaseController<User> {
#Autowired
private UserService userService;
#PostConstruct
public void binder() {
init(this.userService);
}
#GetMapping(value = "/email/{email}")
public ResponseEntity findByEmail(#PathVariable("email") String email) {
return ResponseEntity.ok(userService.findByEmail(email));
}
}
Repository
#Override
public User findByEmail(String email) {
Query query = new Query(Criteria.where("email").is(email).and("status").is(1));
return mongoOperations.findOne(query, User.class);
}
Service
#Override
public User findByEmail(String email) {
return userDao.findByEmail(email);
}
The Error I'm getting is ..
<Map>
<timestamp>1583924335777</timestamp>
<status>406</status>
<error>Not Acceptable</error>
<message>Could not find acceptable representation</message>
<trace>org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:246)
Can anyone please explain What is wrong with my code, And give your valuable solutions
(Basically I need to create Security in API-gateway, in order to control the access of other services)
Try Implementing Feign client as below:
#FeignClient(name = "user-service")
public interface UserByEmail {
#RequestMapping(method = RequestMethod.GET, value = "/email/{email}", consumes = "application/json")
User findByEmail(#PathVariable("email") String email);
}
also make sure the Fields passed over JSON matchers User POJO.
I've got the solution to my Question
FeignClient
#FeignClient(contextId = "user-by-email",name = "user-service")
#Service
public interface UserByEmail {
#RequestMapping(value = "/email/{email}", consumes= MediaType.APPLICATION_JSON_VALUE)
User findByEmail(#RequestParam("email") String email);
}
Controller
#RequestMapping("/test")
public class TestController {
#Autowired
private UserByEmail userByEmail;
#GetMapping(value = "/{email}")
public ResponseEntity testUser(#RequestParam("email") String username) {
return ResponseEntity.ok(userByEmail.findByEmail(username));
}
}
In User-service
Controller
#EnableFeignClients
#RestController
public class UserController extends BaseController<User> {
#Autowired
private UserService userService;
#PostConstruct
public void binder() {
init(this.userService);
}
#GetMapping(value = "/email/{email}")
public ResponseEntity findByEmail(#RequestParam("email") String email) {
return ResponseEntity.ok(userService.findByEmail(email));
}
}
I'm trying to test my Rest controllers from my Spring Boot application and want the controllers to be available under the same path as in production.
For example I have the following Controller:
#RestController
#Transactional
public class MyController {
private final MyRepository repository;
#Autowired
public MyController(MyRepository repository) {
this.repository = repository;
}
#RequestMapping(value = "/myentity/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resource<MyEntity>> getMyEntity(
#PathVariable(value = "id") Long id) {
MyEntity entity = repository.findOne(id);
if (entity == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(entity, HttpStatus.OK);
}
}
Within my application.yml I have configured the context path for the application:
server:
contextPath: /testctx
My test for this controller looks as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = MyController.class, secure=false)
public class MyControllerTest {
#Autowired
private MyRepository repositoryMock;
#Autowired
private MockMvc mvc;
#Test
public void testGet() throws Exception {
MyEntity entity = new MyEntity();
entity.setId(10L);
when(repositoryMock.findOne(10L)).thenReturn(entity);
MockHttpServletResponse response = this.mvc.perform(
MockMvcRequestBuilders.get("/testctx/myentity/10"))
.andReturn().getResponse();
assertEquals(response.getStatus(), 200);
}
#TestConfiguration
public static class TestConfig {
#Bean
MyRepository mockRepo() {
return mock(MyRepository.class);
}
}
}
This test fails since the status code is 404 for the call. If I call /myentity/10 it works. Unfortunately the rest call is initiated by a CDC-Test-Framework (pact) so I cannot change the requested path (containing the context path /testctx). So is there a way to tell spring boot test to start the rest endpoint with a defined context path also during testing?
You could try:
#WebMvcTest(controllers = {MyController.class})
#TestPropertySource(locations="classpath:application.properties")
class MyControllerTest {
#Autowired
protected MockMvc mockMvc;
#Value("${server.servlet.context-path}")
private String contextPath;
#BeforeEach
void setUp() {
assertThat(contextPath).isNotBlank();
((MockServletContext) mockMvc.getDispatcherServlet().getServletContext()).setContextPath(contextPath);
}
protected MockHttpServletRequestBuilder createGetRequest(String request) {
return get(contextPath + request).contextPath(contextPath)...
}
I'm trying to create a simple web-page, which is displaying the data, received from web-service
#Service
#Transactional
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public int saveTopic(Topic topic){
return topicRepository.save(topic).getId();
}
public Iterable<Topic> findAllTopics(){
return topicRepository.findAll();
}
public Topic findTopicByID(Long id){
return topicRepository.findOne(id);
}
public List<Topic> findTopicsByTag(Tag tag){
return topicRepository.findAllByTopicTag(tag);
}
}
topic repository extends CRUD-repository
#Repository
public interface TopicRepository extends CrudRepository<Topic, Long>
{
List<Topic> findAllByTopicTag(Tag currentTag);
}
the controller invokes a service in the following way
#Controller
public class HomeController {
private TopicService service;
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView home(Locale locale, Model model)
{
Iterable<Topic> listTopic = service.findAllTopics();
return new ModelAndView("home", "model", listTopic);
}
}
this string
Iterable listTopic = service.findAllTopics();
throws null-pointer exception. I guess, because the service isn't initialized. How could I perform correct initialization of the service?
You need to autowire the TopicService in HomeController:
#Controller
public class HomeController {
#Aurowired
private TopicService service;