Springboot Mockito Test and Autowired messageSource in controller org.springframework.context.NoSuchMessageException - spring-boot

I have a Springboot app with REST controller and Mockito unit test cases written for it. The problem is I am getting NoSuchMessageException by reading messageSource in the RestController when running the test cases.
But not happening when calling it in actually using Postman or other Rest clients.
(I use Lombok to avoid boilerplate codes).
The Rest Controller code
#RestController
#RequestMapping(value = VERSION + "/product")
#Slf4j
#RequiredArgsConstructor
public class ProductController implements CommonController {
private final ProductService productService;
private final MessageSource messageSource;
#PostMapping(path = "")
public ResponseEntity<CommonResponseDTO> saveProduct(#Valid #RequestBody ProductSaveRequest request) {
return addNewProduct(request);
}
private String getMessage(String key) {
return messageSource.getMessage(key, new Object[0], Locale.getDefault());
}
}
The messageSource Config
#Configuration
public class AppConfig {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages/exception-message", "classpath:messages/success-message");
messageSource.setCacheSeconds(60); //reload messages every 60 seconds
return messageSource;
}
}
Test class
#Slf4j
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ProductController.class)
class ProductControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private ProductService productService;
private static final String PRODUCT_PATH = "/v1/product";
#Test
void saveProduct() throws Exception {
// productService.add to respond back with mockProduct
Mockito.doNothing().when(productService).add(Mockito.any(Product.class));
// Send course as body to /students/Student1/courses
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post(PRODUCT_PATH)
.accept(MediaType.APPLICATION_JSON)
.content("{\n" +
" \"name\": \"Apple\"\n" +
"}")
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
assertEquals(HttpStatus.CREATED.value(), response.getStatus());
}
}
Exception
org.springframework.context.NoSuchMessageException: No message found under code 'success.confirmation.common.added.code' for locale 'en_US'.
But above message exists and successfully appears in actual REST clients.
Project structure
Thanks in advance.

The #WebMvcTest annotation only loads relevant beans (e.g. Filter, #ControllerAdvice, WebMvcConfigurer) for testing your controller. By default, this TestContext doesn't include any custom #Configuration beans.
You have explicitly import your config:
#Slf4j
// #ExtendWith(SpringExtension.class) not needed as part of #WebMvcTest with recent Spring Boot versions
#Import(AppConfig.class)
#WebMvcTest(value = ProductController.class)
class ProductControllerTest {
// your test
}
In case you're relying on the auto-configuration of the MessageSource you can enable it for your test with #ImportAutoConfiguration(MessageSourceAutoConfiguration.class).

Related

Spring-Boot 2.3.0.RELEASE Unable to autowire RestTemplate for JUnit 5 test

I have configured the necessary Beans in #Configuration class but have not been able to get the RestTemplate injected into my test class for testing.
#Configuration
public class AppConfig {
#Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
#Bean
public RestTemplate restTemplate(ProtobufHttpMessageConverter converter) {
RestTemplate http2Template = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
List<HttpMessageConverter<?>> converters = http2Template.getMessageConverters();
converters.add(converter);
http2Template.setMessageConverters(converters);
return http2Template;
}
}
Test class:
#ExtendWith(SpringExtension.class)
#AutoConfigureWebClient(registerRestTemplate = true)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class})
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
Any advice and insight is appreciated
The classes attribute of the annotation #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class}) takes component classes to load the application context. You should not put in here anything except your main Spring Boot class or leave it empty.
Furthermore #AutoConfigureWebClient(registerRestTemplate = true) as you want to use the bean you configure inside your application (at least that's what I understood from your question).
So your test setup should look like the following:
// #ExtendWith(SpringExtension.class) can be omitted as it is already part of #SpringBootTest
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
This should now start your whole Spring Boot context in dev profile and you should have access to all your beans you define inside your production code like AppConfig.

how to create RestController in test directory for SpringBoot Application

Im currently writing integration test for SpringBoot Application .
It's functionality is to receive/send request from outside and forward/receive them to another application(APP_2). So there are two systems which needs to be mocked outside System and APP_2 .
HomeController
#Controller
public class HomeController {
#Autowired
ForwardController forwardController;
#RequestMapping("/")
public #ResponseBody
String greeting() {
return forwardController.processGET().toString();
}
}
ForwardController
#Service
public class ForwardController {
#Autowired
private RestTemplate restTemplate;
#Autowired
private Environment environment;
private ResponseEntity sendRequest(String url, HttpMethod method, HttpEntity requestEntity, Class responseType, Object... uriVariables) {
return restTemplate.exchange( url, method, requestEntity, responseType,uriVariables);
}
public ResponseEntity processGET()
{
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> entity = new HttpEntity<>(headers);
String app_2_url = environment.getProperty(Constants.APP_2_URL);
ResponseEntity<String> response = sendRequest(app_2_url,HttpMethod.GET,entity,String.class);
return response;
}
}
APP_2_CONTROLLER
#Controller
public class App_2_Controller {
#RequestMapping("/app2Stub")
public #ResponseBody
String greeting() {
return "Hello End of world";
}
}
Test Class which simulates the external request behavior to the system:
HTTP_request_Test
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,classes = Application.class)
public class HttpRequestTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Autowired
private Environment environment;
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
String.class)).contains("Hello End of world");
}
}
Here in this test class I'm overriding the properties by having two property file. So when we run test the request would be sent to App_2_Controller ( Mock in my project ) rather than the real App .
QUESTION :
Is there any way to have the APP_2_CONTROLLER inside the test folder ? This is because I don't want to expose the unwanted test endpoint in my Actual application .
Here in the above project , Im changing the URL with properties. Is there a better way to put a controller for the same URL. For simplicity sake lets assume, app_2 url is app.com:9000/serve
Spring already comes with a MockRestServiceServer, that makes this a lot easier so that you don't have to create your own dummy controllers (App_2_Controller). So in your case, you can remove that controller, and write a test like this for ForwardController:
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class ForwardControllerTest {
#Autowired
private RestTemplate restTemplate;
#Autowired
private ForwardController forwardController; // Your service
private MockRestServiceServer server;
#Before
public void setUp() {
server = MockRestServiceServer.bindTo(restTemplate).build();
}
#Test
public void processGet_returnsResponseFromAPI() {
server.expect(once(), requestTo("http://app.com:9000/serve"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("Hello End of world", MediaType.TEXT_PLAIN));
assertThat(forwardController.processGET().getBody()).isEqualTo("Hello End of world"));
}
}
Additionally, you can create a separate test for your actual controller (ForwardController is just a service), mock ForwardController and use MockMvc:
#RunWith(SpringRunner.class)
#WebMvcTest
public class HomeControllerTest {
#Autowired
private HomeController homeController;
#Autowired
private MockMvc mockMvc;
#MockBean
private ForwardController forwardController;
#Test
public void greeting_usesForwardController() {
when(forwardController.expectGET()).thenReturn("Hello End of world");
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello End of world")));
}
}
In this case, you'll end up with two tests:
One test to verify that RestTemplate is used to capture the proper response from your external REST API.
Another test to verify that HomeController just forwards whatever ForwardController responds.

Using #RestClientTest in spring boot test

I want to write a simple test using #RestClientTest for the component below (NOTE: I can do it without using #RestClientTest and mocking dependent beans which works fine.).
#Slf4j
#Component
#RequiredArgsConstructor
public class NotificationSender {
private final ApplicationSettings settings;
private final RestTemplate restTemplate;
public ResponseEntity<String> sendNotification(UserNotification userNotification)
throws URISyntaxException {
// Some modifications to request message as required
return restTemplate.exchange(new RequestEntity<>(userNotification, HttpMethod.POST, new URI(settings.getNotificationUrl())), String.class);
}
}
And the test;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}
But i get error that No qualifying bean of type 'org.springframework.web.client.RestTemplate' available. Which is right of course as i havn't mocked it or used #ContextConfiguration to load it.
Isn't #RestClientTest configures a RestTemplate? or i have understood it wrong?
Found it! Since i was using a bean that has a RestTemplate injected directly, we have to add #AutoConfigureWebClient(registerRestTemplate = true) to the test which solves this.
This was in the javadoc of #RestClientTest which i seem to have ignored previously.
Test which succeeds;
#RunWith(SpringRunner.class)
#RestClientTest(NotificationSender.class)
#ActiveProfiles("local-test")
#AutoConfigureWebClient(registerRestTemplate = true)
public class NotificationSenderTest {
#MockBean
private ApplicationSettings settings;
#Autowired
private MockRestServiceServer server;
#Autowired
private NotificationSender messageSender;
#Test
public void testSendNotification() throws Exception {
String url = "/test/notification";
UserNotification userNotification = buildDummyUserNotification();
when(settings.getNotificationUrl()).thenReturn(url);
this.server.expect(requestTo(url)).andRespond(withSuccess());
ResponseEntity<String> response = messageSender.sendNotification(userNotification );
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
private UserNotification buildDummyUserNotification() {
// Build and return a sample message
}
}

Spring MVC dynamically configured getting 404 but works with MockMVC

I have the following controller (notice how it's configured):
#Controller
public class MyController {
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
#PostConstruct
private void createRequestMapping() throws Exception {
RequestMappingInfo commandExecutionRequestMappingInfo =
RequestMappingInfo
.paths(endpointUri)
.methods(RequestMethod.POST)
.consumes(MediaType.APPLICATION_JSON_VALUE)
.produces(MediaType.APPLICATION_JSON_VALUE)
.build();
requestMappingHandlerMapping
.registerMapping(commandExecutionRequestMappingInfo, this,
MyController.class.getDeclaredMethod("execute", String.class));
RequestMappingInfo getAvailableCommandsRequestMappingInfo =
RequestMappingInfo
.paths(endpointUri)
.methods(RequestMethod.GET)
.build();
requestMappingHandlerMapping
.registerMapping(getAvailableCommandsRequestMappingInfo, this,
MyController.class
.getDeclaredMethod("getAvailableCommands", Model.class));
}
#ResponseBody
private String execute(#RequestBody String command) {
...
}
private String getAvailableCommands(Model model) {
model.addAttribute("docs", handlerDocs);
return "docs";
}
There is a docs.html page which sits in main/resources/templates/docs.html.
When I run a test with MockMvc, it see the output of that page:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class ApplicationConfig {
}
Here is the test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = ApplicationConfig.class)
#AutoConfigureMockMvc
public class SpringRestCommanderTest {
#Autowired
private MockMvc mvc;
#Test
public void testGetAvailableCommands() throws Exception {
mvc.perform(get("/execute")).andDo(print());
}
}
However, when I run this same thing in a real app, it gives me a 404 for the GET request when I try it in the browser.
What's strange is that the SAME /execute works when issue a POST request to it. As you can see above, the dynamic mapping is very similar between POST and GET.
What am I doing wrong here?

Mock object is not getting injected in Service Class when Cucumber is used with Mockito

We are calling a third party service which I would like to mock and not call it. For Some reason, the mock RestTemplate doesn't get injected and the class has real "RestTemplate" object.
My cucumber class look like this
#RunWith(Cucumber.class)
#CucumberOptions(plugin = { "pretty", "html:build/cucumber",
"junit:build/cucumber/junit-report.xml" },
features = "src/test/resources/feature",
tags = { "#FunctionalTest","#In-Progress", "~#TO-DO" },
glue= "com.arrow.myarrow.service.order.bdd.stepDef")
public class CucumberTest {
}
and the StepDefinition looks like this
#ContextConfiguration(loader = SpringBootContextLoader.class, classes =
OrderServiceBoot.class)
#WebAppConfiguration
#SpringBootTest
public class BaseStepDefinition {
#Autowired
WebApplicationContext context;
MockMvc mockMvc;
#Rule public MockitoRule rule = MockitoJUnit.rule();
RestTemplate restTemplate = mock(RestTemplate.class);
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
//Telling rest template what to do
when(restTemplate.exchange(Mockito.anyString(), Mockito.
<HttpMethod>any(), Mockito.<HttpEntity<?>>any(), Mockito.
<Class<UserProfile>>any()))
.thenReturn(new ResponseEntity<>(userProfile,
HttpStatus.OK));
}
This is my service class looks like
#Autowired
RestTemplate restTemplate;
public UserProfile getUserProfile(OAuth2Authentication auth){
ResponseEntity<UserProfile> response
=restTemplate.exchange("http://localhost:8084/api/v1.0/user/profile", HttpMethod.GET,new HttpEntity<>(new HttpHeaders()),UserProfile.class);
return response.getBody();
}
In the service class, the RestTemplate restTemplate is not mocked, it contains the real object so it is trying to call the real service which is not intended.
Does anyone knows why Mocking isn't working here?
The way it worked for me is by creating a class in TestFolder and then defining a new bean for resttemplate which generates the MockRestTemplate instance.
#Configuration
#Profile("local")
public class CucumberMockConfig {
#Bean
#Primary
public RestTemplate getRestRemplate() {
return mock(RestTemplate.class);
}
}
In test class use (Dont use #Mock or Mock(restTemplate), as you don't want a new object)
#Autowired
RestTemplate restTemplate
#Before
public void setup() throws JsonProcessingException {
UserProfile userProfile = new UserProfile();
userProfile.setCompany("myCompany");
when(restTemplate.exchange(Mockito.endsWith("/profile"),
Mockito.<HttpMethod>eq(HttpMethod.GET),
Mockito.<HttpEntity<?>>any(),
Mockito.eq(UserProfile.class)))
.thenReturn(ResponseEntity.ok().body(userProfile));
}
and in service/config class use
#Autowired
RestTemplate restTemplate

Resources