How to use MockMvc and MockRestServiceServer in Java #SpringBootTest - java

I am attempting to write a #SpringBootTest which will both issue REST requests and mock responses. I want to do this because my spring application receives REST requests and in response it fetches information from another source using REST. I would like to trigger my application by initiating a REST GET (MockMvc), but would also like to mock the other source's response (MockRestServiceServer).
Here is a sanitized version of the test, with my application removed:
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.client.RestTemplate;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#Slf4j
#ExtendWith(SpringExtension.class)
#SpringBootTest
#AutoConfigureMockMvc
class RestTest {
private static final String URI = "/my-test-uri";
private static final String RESPONSE = "my-response";
#Autowired
private MockMvc mockMvc;
#Test
void testRest() throws Exception {
log.info("Initiating request for GET {}", URI);
mockMvc.perform(get(URI))
.andExpect(status().isOk())
.andExpect(content().string(containsString(RESPONSE)));
}
#TestConfiguration
static class TestConfig {
#Primary
#Bean
public RestTemplateBuilder restTemplateBuilder() {
RestTemplate restTemplate = new RestTemplate();
MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
server.expect(requestTo(URI))
.andRespond(withSuccess(RESPONSE, MediaType.APPLICATION_JSON));
log.info("Mocking response to GET {}: {}", URI, RESPONSE);
RestTemplateBuilder mockBuilder = mock(RestTemplateBuilder.class);
when(mockBuilder.build()).thenReturn(restTemplate);
return mockBuilder;
}
}
}
The test fails because it does not receive the response defined in TestConfig.restTemplateBuilder. I am providing both the REST requestor (testRest() method) and the REST responder (TestConfig.restTemplateBuilder). What am I doing wrong?
Here's the failure:
Status expected:<200> but was:<404>
Expected :200
Actual :404
<Click to see difference>
java.lang.AssertionError: Status expected:<200> but was:<404>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:627)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:196)
at com.mycompany.RestTest.testRest(RestTest.java:54)
...
at java.base/java.lang.Thread.run(Thread.java:830)

Related

BadRequest 400 when mocking a resttemplate exchange method

I am using mockito to test a resttemplate exchange method, but I am getting a BadRequest 400 : "{"timestamp":"2022-06-09T20:27:37.950+00:00","status":400. I am using junit 5 as well.
This is my code:
import com.fasterxml.jackson.core.JsonProcessingException;
import exception.HmacExeption;
import model.Payload;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
#ExtendWith(MockitoExtension.class)
class GithubWebHookServiceTest {
#Mock
private RestTemplate restTemplate;
#InjectMocks
private CarService carService;
#Test
public void test() throws HmacExeption, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
ResponseEntity<String> responseEntity = new ResponseEntity<String>("sampleBodyString", HttpStatus.ACCEPTED);
Mockito.
when(restTemplate.exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<String>> any()
)
).thenReturn(responseEntity);
carService.executeRestCall(new Payload());
}
}
Not sure, what am I missing.
Thanks!!

Spring boot , i18n error, wrong implementation of LocaleResolver is choosen

I am developing a spring boot based application that supports internationalisation
My env:
Jdk11
Spring boot 2.7.0
Spring Security
Here is my web mvc conf
WebMvcConf.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
#Configuration
public class WebMvcConf implements WebMvcConfigurer {
public static final Locale idnLocale = Locale.forLanguageTag("id-ID");
#Bean
public LocaleResolver defaultLocaleResolver(){
var resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(idnLocale);
return resolver;
}
#Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
var interceptor = new LocaleChangeInterceptor();
interceptor.setIgnoreInvalidLocale(false);
interceptor.setParamName("lang");
return interceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
And here is my security config:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
#Configuration
#EnableWebSecurity
public class SecurityConf{
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()//enable cors
.and()
.csrf().disable()//disable csrf
.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))//stateless session (Rest)
.authorizeHttpRequests(authz->authz
.antMatchers(HttpMethod.GET,"/").permitAll()
.antMatchers(HttpMethod.POST,"/users/login","users/register","users/forgot-password").permitAll()
.antMatchers(HttpMethod.PATCH,"/users/password").permitAll()
.anyRequest().authenticated());//authorize any request except ignored endpoint above
return httpSecurity.build();
}
}
Here is the structure of the message properties:
Here is my application.yml content:
Here is the content of messages_en.properties:
rest.welcome.ok=Welcome to ABC Backend Service, have fun!!!
rest.users.login.ok=Successfully Logged in
#Error Message
rest.500.err=Internal Server Error
rest.422.err=Constraint Violation Error
rest.400.err=Invalid request body
rest.required-req-body-missing.err=Required request body is missing
Here is content of messages.properties:
rest.welcome.ok=Selamat Datang di Service ABC, selamat bersenang-senang!!!
rest.users.login.ok=Login sukses
#Error message
rest.500.err=Kesalahan Internal di sistem
rest.422.err=Error pelanggaran constraint
rest.400.err=Kesalahan pada request body
rest.required-req-body-missing.err=Request body (payload) yang dibutuhkan tidak ditemukan
Here is my test to test the message source:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.MessageSource;
import java.util.Locale;
#SpringBootTest
public class MessageSourceTest {
#Autowired
private MessageSource messageSource;
#BeforeEach
void init(){
}
#Test
public void message_provided_should_correctly_localized(){
String msgEnglish = messageSource.getMessage("rest.500.err",null, new Locale("en"));
String msgIndo = messageSource.getMessage("rest.500.err",null,Locale.forLanguageTag("id-ID"));
Assertions.assertEquals("Internal Server Error",msgEnglish);
Assertions.assertEquals("Kesalahan Internal di sistem",msgIndo);
}
}
And the test is pass:
But when I try to hit a mvc rest controller and add query parameter lang=en , I always got an error response saying Cannot change HTTP accept header - use a different locale resolution strategy
So I debug the LocaleChangeInterceptor and found that spring boot provide a wrong implementation of the LocaleResolver, they provide AcceptHeaderResolver, which what I want is the SessionLocaleResolver as I state it in my WebMvcConf class. See following picture:
Anyone knows what is wrong and how to fix it?any response will be appreciated

How to addFilter for custom jwt header authorization

For some reason I cannot add a filter that will take over the authorization phase of the filter chain. The defaultChain is reading out on start: "Will not secure any request", but it seems like my request are secured when I test, however It will not trigger my filter. If I could get some help on this matter it would be greatly appreciated!
WEBCONFIG
package com.legacybanking.legacyBankingAPI.security.securityConfig;
import com.legacybanking.legacyBankingAPI.security.jwt.JwtPasswordValidator;
import com.legacybanking.legacyBankingAPI.services.CustomerService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.util.List;
#Configuration
#AllArgsConstructor
#EnableWebSecurity
#Slf4j
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final CustomerService customerService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customerService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
#Bean
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
log.info("ITS NOT WORKING");
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.addFilter(new JwtPasswordValidator(authenticationManager()))
.authorizeRequests()
// ANT MACHERS = WHITELISTING
.antMatchers("/api/**")
.permitAll()
.anyRequest()
.authenticated();
}
}
JWTCONFIG
package com.legacybanking.legacyBankingAPI.security.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.legacybanking.legacyBankingAPI.models.Customer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.stream.Collectors;
#Slf4j
public class JwtPasswordValidator extends UsernamePasswordAuthenticationFilter {
#Autowired
private final AuthenticationManager authenticationManager;
public JwtPasswordValidator(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication( HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
System.out.println("request = " + request.getParameter("email"));
System.out.println("request = " + request.getParameter("password"));
String email = request.getParameter("email");
String password = request.getParameter("password");
log.info("Test Loging -> {}, {}",email,password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email,password);
return authenticationManager.authenticate(authenticationToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
System.out.println(authResult);
User user = (User) authResult.getPrincipal();
String key = "secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret";
Algorithm algorithm = Algorithm.HMAC256(key.getBytes());
String access_token = JWT.create()
.withSubject(user.getUsername())
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.withIssuer(request.getRequestURI())
.withClaim("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.withIssuer(request.getRequestURI())
.sign(algorithm);
System.out.println(access_token);
log.info("This is the access token: {}", access_token);
response.addHeader("Authorization", "Bearer " + access_token);
response.setHeader("access_token", "token" + access_token);
}
}
You have to register the JwtPasswordValidator class inside the AppSecurityConfig cofig() method as follows:
http.addFilter(JwtPasswordValidator);
also you may need to create a CustomAuthorizationFilter which extends OncePerRequestFilter class and register it inside the config method as follows:
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
if need more help do not hesitate to ask.

Spring Controller gives me 404 status error

So I am attempting to write a basic Rest Controller in Spring Tool Suite with Spring annotations where I hit the endpoint "/health" and I receive the following message upon hitting the endpoint "Kafka Streaming App is healthy".
The only error I receive is the 404 status error when I check on the browser and I have already tried logging please help. I think the problem might be how I am calling the Controller in the Main Application class in Spring.
Main Application Class
package com.eds.kafka.streaming.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
//#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
//#Configuration
//#EntityScan(basePackages = {"com.eds.kafka.streaming.model"})
//#ComponentScan(basePackages = { "com.eds.kafka.streaming.service", "com.eds.kafka.streaming.util", "com.eds.kafka.streaming.config", "com.eds.kafka.streaming.controller"}) //"com.tmobile.eds.infosec.volt.encr",
#ComponentScan(basePackages = {"com.eds.kafka.streaming"})
#SpringBootApplication
public class KafkaStreamingAppContainerApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(KafkaStreamingAppContainerApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(KafkaStreamingAppContainerApplication.class);
}
}
Controller Class
package com.eds.kafka.streaming.controller;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
#EnableAutoConfiguration
#PropertySource("classpath:application-${spring.profiles.active}.properties")
#RestController
public class MainController {
#RequestMapping(method = RequestMethod.GET, value = "/health", produces = "application/json")
#ResponseBody
public ResponseEntity home() {
System.out.println("Controller being configured!");
return new ResponseEntity("Kafka Streaming App is Healthy", HttpStatus.OK);
}
}
Please let me know if you have any advice and if needed I can provide a zipped version of the project if that is needed just message me.

SpringBoot Swagger2 rest API documentation is not loading

I am just trying out to get the documentation for my small REST API written in in spring with swagger2 inclusion. When i try to access my swagger page the following error is shown in browser console.
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Sep 25 21:09:08 IST 2017
There was an unexpected error (type=Not Found, status=404).
No message available
My code snippets are below mentioned.
package com.example.demo;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
#RestController
#RequestMapping(value="/rooms")
#Api(value="rooms", tags=("rooms"))
public class RoomController {
#Autowired
private RoomRepositery roomRepo;
#RequestMapping(method = RequestMethod.GET)
#ApiOperation(value="Get All Rooms", notes="Get all rooms in the system", nickname="getRooms")
public List<Room> findAll(#RequestParam(name="roomNumber", required=false)String roomNumber){
if(StringUtils.isNotEmpty(roomNumber)) {
return Collections.singletonList(roomRepo.findByRoomNumber(roomNumber));
}
return (List<Room>) this.roomRepo.findAll();
}
}
App class
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import static springfox.documentation.builders.PathSelectors.any;
#SpringBootApplication
#EnableSwagger2
public class RoomServiceApp {
#Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2).groupName("Room").select()
.apis(RequestHandlerSelectors.basePackage("com.example.demo"))
.paths(any()).build().apiInfo(new ApiInfo("Room Services",
"A set of services to provide data access to rooms", "1.0.0", null,
new Contact("Frank Moley", "https://twitter.com/fpmoles", null),null, null));
}
public static void main(String[] args) {
SpringApplication.run(RoomServiceApp.class, args);
}
}
I am not able to found what i am missing here. Can any one help me out?
Thanks
You have to use http://localhost:9090/swagger-ui.html for swagger UI and http://localhost:9090/v2/api-docs?group=Room for JSON API.
I used the same code and find attached screenshot.
refer to this project for more details.
Request mapping at method level should have URI path like below for findAll method. Consumes and produces also required if there is input passed to method and output returned from method.
The reason is empty mapping value at method level will result in 'host:port/contextPath/' for which swagger will return 404.
#RequestMapping(method = { RequestMethod.GET }, value = "/roomList", consumes = {
MediaType.ALL}, produces = { MediaType.ALL})

Categories

Resources