JUnit5 - Rest API controller having custom spring validator is failing - rest

I have a controller that accepts path parameter called 'jobName'. The #ValidateJobName is the custom validator that validates the user input. If the input is wrong then it throws the error below
"Invalid Job name, valid job names are: vendor, service, product,
pricing, currency, contract"
The issue I am facing is that, when I am testing my rest controller API the test case always fails by returning the above error even when the job name is one of the acceptable values but when I remove #ValidateJobName custom annotation from the controller my test cases gets passed.
The #ValidateJobName and Controller works all good when triggered from Postman client but when I do unit testing the test case fails.
I have tried lot of blogs and googled but could not get a solution, Below are my Controller and JUnit testcase.
Please help!
JobController.java
#Validated
#Slf4j
#RestController
public class JobController {
#Autowired
ReportService reportService;
#Autowired
ReportConfig reportConfig;
#RequestMapping(value = "/importjob/{jobName}", method = RequestMethod.GET)
ResponseEntity<DataIntegrationResponse> getReport(#PathVariable #ValidateJobName String jobName) throws Exception {
log.info("Received a request to launch the " + jobName + " Job");
return reportService.getReport(jobName);
}
}
JobControllerTest.java
#ExtendWith(MockitoExtension.class)
#WebMvcTest(JobController.class)
#AutoConfigureMockMvc
public class JobControllerTest {
#MockBean
ReportService reportService;
#MockBean
ReportConfig rep;
#MockBean
JobMapping jmap;
#Autowired
public MockMvc mockMvc;
#Test
public void testGetReport() throws Exception {
String jobNameInput="vendor";
HttpStatus httpStatus = HttpStatus.OK;
String fitsReportName = "idex_fits_vendor.csv";
String jobName = "WFitsVendorJob";
String jobStatus = "STARTED";
Long jobInstanceId = 1022L;
String message = "WFitsVendorJob triggered successfully.";
DataIntegrationResponse response = new DataIntegrationResponse(LocalDateTime.now(), httpStatus, fitsReportName, jobName, jobStatus, jobInstanceId, message);
ResponseEntity<DataIntegrationResponse> responseEntity = new ResponseEntity<DataIntegrationResponse>(response, HttpStatus.OK);
Mockito.when(reportService.getReport(jobNameInput)).thenReturn(responseEntity);
mockMvc.perform(get("/importjob/{jobName}", "vendor")).andExpect(status().isOk());
}
JobNameValidator.java
#Component
public class JobNameValidator implements ConstraintValidator<ValidateJobName, String>{
#Autowired
private JobMapping jobMap;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value!=null && !jobMap.getMappings().containsKey(value)) { return false; }
return true;
}
}
ValidateJobName.java - interface
#Documented
#Constraint(validatedBy = JobNameValidator.class)
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
public #interface ValidateJobName {
String message() default "Invalid Job name, valid job names are: vendor, service, product, pricing, currency, contract";
Class<?>[] groups() default {};
Class<? extends Payload> [] payload() default {};
}

This is because you use a mock of JobMapping
#MockBean
JobMapping jmap;
Your JobNameValidator receive a Mock and doesn't know what to return when calling the containsKey method.
First solution is to tell what to do with this mock :
Mockito.when(jobMapping.getMappings()).thenReturn(// Map containing "vendor");
The second solution is to import your real JobMapping class instead of a mock:
#ExtendWith(MockitoExtension.class)
#WebMvcTest(JobController.class)
#AutoConfigureMockMvc
#Import(JobMapping.class)
class JobControllerTest {
// #MockBean
// JobMapping jmap;
}

Related

spring-boot test: #get request returns with body null

while doing Content-negotiation testing mock GET returns with null in response body although response status is 200.
java.lang.AssertionError: Response header 'Content-Type'
Expected :application/json;charset=UTF-8
Actual :null
here is full test class code. I want to verify that content type is json.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
Controller controller;
#Test
public void test() throws Exception {
mockMvc.perform(get("/query?mediaType=json"))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE));
}}
here is my controller's endpoint.
#RestController
public class Controller {
#RequestMapping(value = "/query", produces = {"application/json", "application/xml"}, method = RequestMethod.GET)
public #ResponseBody ResultSet getResults(
final HttpServletRequest request
) throws Throwable {
// logic ...
SearchService search = (SearchService) context.getBean("search");
ResultSet result = search.getResults();
return result;
}
Any thoughts why Body would return as null?
The issue is with your Controller definition in your Test class. As you are testing your Controller, you should be using an actual instance of it. Get you mockMvc instance for this Controller as below (you can do it in your #Before annotated setup method):
mockMvc = MockMvcBuilders.standaloneSetup(new Controller()).build();

How to get oAuth2 access token when user call signup rest API in springboot?

currently I am working on Springboot security, its quite new for me. I followed youtube video tutorial Video
I am getting oauth2 access_token successfully when I use bellow code snippet:-
#SpringBootApplication
public class MathifyApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(MathifyApplication.class, args);
}
#Autowired
public void authenticationManager(AuthenticationManagerBuilder builder, UserRepository repository, UserService service) throws Exception {
//Setup a default user if db is empty
User students = new User("stu1", "user", "user", "abc#gmail.com", "1234567890", "12th", "dwarka sec-12",
0, 0 , "may/29/2017", "", Arrays.asList(new Role("USER"), new Role("ACTUATOR")));
if (repository.count()==0){
service.save(students);
}
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return userName -> new CustomUserDetails(repository.findByUsername(userName));
}
}
And Controller Class is:-
#RestController
public class LoginController {
#Autowired
private UserService userService;
#RequestMapping(value = "/mathify/getuser/{userId}", method = RequestMethod.GET)
public User getUser(#PathVariable String userId){
System.out.println("Userid "+userId);
return userService.getUser(userId);
}
#RequestMapping(method = RequestMethod.POST, value="/mathify/signup")
public User register(#RequestBody User user){
return userService.doSignup(user);
}
#GetMapping(value="/hi")
public String test(){
return "Oh ! I am fine without secuirity";
}
}
With above code snippet I can get access_token(/oauth/token), and I can also call other controller class private APIs without any issue.
but there is a problem with above code. What? In above code snippet User is hard coded, but when I want to get access_token at the time of user signup it's giving an exception.
2017-06-18 11:04:05.689 ERROR 8492 --- [nio-8080-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Cannot apply org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer#6b66d7ac to already built object] with root cause
java.lang.IllegalStateException: Cannot apply org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer#6b66d7ac to already built object
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.add(AbstractConfiguredSecurityBuilder.java:196) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.apply(AbstractConfiguredSecurityBuilder.java:133) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.apply(AuthenticationManagerBuilder.java:290) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.userDetailsService(AuthenticationManagerBuilder.java:187) ~[spring-security-config-4.2.2.RELEASE.jar:4.2.2.RELEASE]
at com.techiesandeep.mathify.controller.LoginController.register(LoginController.java:40) ~[classes/:na]
for achieving above described feature I did some changes in my Application and Controller
Application Class is As:-
#SpringBootApplication
public class MathifyApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(MathifyApplication.class, args);
}
}
and Controller class is as:-
#RestController
public class LoginController {
#Autowired
private UserService userService;
#Autowired
AuthenticationManagerBuilder builder;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private UserRepository repository;
#RequestMapping(value = "/mathify/getuser/{userId}", method = RequestMethod.GET)
public User getUser(#PathVariable String userId){
System.out.println("Userid "+userId);
return userService.getUser(userId);
}
#RequestMapping(method = RequestMethod.POST, value="/user/signup")
public User register(#RequestBody User user) throws Exception {
User u = userService.doSignup(user);
builder.userDetailsService(userDetailsService(repository)).passwordEncoder(passwordEncoder);
return u;
}
private UserDetailsService userDetailsService(final UserRepository repository) {
return userName -> new CustomUserDetails(repository.findByUsername(userName));
}
#GetMapping(value="/hi")
public String test(){
return "Oh ! I am fine without secuirity";
}
}
Any help would be appreciable.thanks
You can call another POST request to get access token.
I am not sure it's the best way, but worked fine with me.
Example code snip inside Signup Request mapping:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", auth_header);
/*auth_header should be Autorization header value that captured from signup request, which is generated by Basic Auth with clientID and secret, for example, "Basic bXktdHJ1c3RlZC1jbGllbnQ6c2VjcmV0" */
HttpEntity<String> entity = new HttpEntity<String>("",headers);
String authURL = "http://localhost:8080/oauth/token?grant_type=password&username=yourusername&password=yourpassword";
ResponseEntity<String> response = restTemplate.postForEntity(authURL, entity, String.class);
System.out.println(response.getBody());

How can I change the feign URL during the runtime?

#FeignClient(name = "test", url="http://xxxx")
How can I change the feign URL (url="http://xxxx") during the runtime? because the URL can only be determined at run time.
You can add an unannotated URI parameter (that can potentially be determined at runtime) and that will be the base path that will be used for the request. E.g.:
#FeignClient(name = "dummy-name", url = "https://this-is-a-placeholder.com")
public interface MyClient {
#PostMapping(path = "/create")
UserDto createUser(URI baseUrl, #RequestBody UserDto userDto);
}
And then the usage will be:
#Autowired
private MyClient myClient;
...
URI determinedBasePathUri = URI.create("https://my-determined-host.com");
myClient.createUser(determinedBasePathUri, userDto);
This will send a POST request to https://my-determined-host.com/create (source).
Feign has a way to provide the dynamic URLs and endpoints at runtime.
The following steps have to be followed:
In the FeignClient interface we have to remove the URL parameter. We have to use #RequestLine annotation to mention the REST method (GET, PUT, POST, etc.):
#FeignClient(name="customerProfileAdapter")
public interface CustomerProfileAdaptor {
// #RequestMapping(method=RequestMethod.GET, value="/get_all")
#RequestLine("GET")
public List<Customer> getAllCustomers(URI baseUri);
// #RequestMapping(method=RequestMethod.POST, value="/add")
#RequestLine("POST")
public ResponseEntity<CustomerProfileResponse> addCustomer(URI baseUri, Customer customer);
#RequestLine("DELETE")
public ResponseEntity<CustomerProfileResponse> deleteCustomer(URI baseUri, String mobile);
}
In RestController you have to import FeignClientConfiguration
You have to write one RestController constructor with encoder and decoder as parameters.
You need to build the FeignClient with the encoder, decoder.
While calling the FeignClient methods, provide the URI (BaserUrl + endpoint) along with rest call parameters if any.
#RestController
#Import(FeignClientsConfiguration.class)
public class FeignDemoController {
CustomerProfileAdaptor customerProfileAdaptor;
#Autowired
public FeignDemoController(Decoder decoder, Encoder encoder) {
customerProfileAdaptor = Feign.builder().encoder(encoder).decoder(decoder)
.target(Target.EmptyTarget.create(CustomerProfileAdaptor.class));
}
#RequestMapping(value = "/get_all", method = RequestMethod.GET)
public List<Customer> getAllCustomers() throws URISyntaxException {
return customerProfileAdaptor
.getAllCustomers(new URI("http://localhost:8090/customer-profile/get_all"));
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<CustomerProfileResponse> addCustomer(#RequestBody Customer customer)
throws URISyntaxException {
return customerProfileAdaptor
.addCustomer(new URI("http://localhost:8090/customer-profile/add"), customer);
}
#RequestMapping(value = "/delete", method = RequestMethod.POST)
public ResponseEntity<CustomerProfileResponse> deleteCustomer(#RequestBody String mobile)
throws URISyntaxException {
return customerProfileAdaptor
.deleteCustomer(new URI("http://localhost:8090/customer-profile/delete"), mobile);
}
}
I don`t know if you use spring depend on multiple profile.
for example: like(dev,beta,prod and so on)
if your depend on different yml or properties. you can define FeignClientlike:(#FeignClient(url = "${feign.client.url.TestUrl}", configuration = FeignConf.class))
then
define
feign:
client:
url:
TestUrl: http://dev:dev
in your application-dev.yml
define
feign:
client:
url:
TestUrl: http://beta:beta
in your application-beta.yml (I prefer yml).
......
thanks god.enjoy.
use feign.Target.EmptyTarget
#Bean
public BotRemoteClient botRemoteClient(){
return Feign.builder().target(Target.EmptyTarget.create(BotRemoteClient.class));
}
public interface BotRemoteClient {
#RequestLine("POST /message")
#Headers("Content-Type: application/json")
BotMessageRs sendMessage(URI url, BotMessageRq message);
}
botRemoteClient.sendMessage(new URI("http://google.com"), rq)
You can create the client manually:
#Import(FeignClientsConfiguration.class)
class FooController {
private FooClient fooClient;
private FooClient adminClient;
#Autowired
public FooController(ResponseEntityDecoder decoder, SpringEncoder encoder, Client client) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "http://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "http://PROD-SVC");
}
}
From documentation: https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#_creating_feign_clients_manually
In interface you can change url by Spring annotations. The base URI is configured in yml Spring configuration.
#FeignClient(
name = "some.client",
url = "${some.serviceUrl:}",
configuration = FeignClientConfiguration.class
)
public interface SomeClient {
#GetMapping("/metadata/search")
String search(#RequestBody SearchCriteria criteria);
#GetMapping("/files/{id}")
StreamingResponseBody downloadFileById(#PathVariable("id") UUID id);
}
Use #PathVariable like this:
#Service
#FeignClient(name = "otherservicename", decode404 = true)
public interface myService {
#RequestMapping(method = RequestMethod.POST, value = "/basepath/{request-path}")
ResponseEntity<String> getResult(#RequestHeader("Authorization") String token,
#RequestBody HashMap<String, String> reqBody,
#PathVariable(value = "request-path") String requestPath);
}
Then from service, construct the dynamic url path and send the request:
String requestPath = "approve-req";
ResponseEntity<String> responseEntity = myService.getResult(
token, reqBody, requestPath);
Your request url will be at: "/basepath/approve-req"
I prefer to build feign client by configuration to pass a url at run time (in my case i get the url by service name from consul discovery service)
so i extend feign target class as below:
public class DynamicTarget<T> implements Target<T> {
private final CustomLoadBalancer loadBalancer;
private final String serviceId;
private final Class<T> type;
public DynamicTarget(String serviceId, Class<T> type, CustomLoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
this.serviceId = serviceId;
this.type = type;
}
#Override
public Class<T> type() {
return type;
}
#Override
public String name() {
return serviceId;
}
#Override
public String url() {
return loadBalancer.getServiceUrl(name());
}
#Override
public Request apply(RequestTemplate requestTemplate) {
requestTemplate.target(url());
return requestTemplate.request();
}
}
var target = new DynamicTarget<>(Services.service_id, ExamsAdapter.class, loadBalancer);
package commxx;
import java.net.URI;
import java.net.URISyntaxException;
import feign.Client;
import feign.Feign;
import feign.RequestLine;
import feign.Retryer;
import feign.Target;
import feign.codec.Encoder;
import feign.codec.Encoder.Default;
import feign.codec.StringDecoder;
public class FeignTest {
public interface someItfs {
#RequestLine("GET")
String getx(URI baseUri);
}
public static void main(String[] args) throws URISyntaxException {
String url = "http://www.baidu.com/s?wd=ddd"; //ok..
someItfs someItfs1 = Feign.builder()
// .logger(new FeignInfoLogger()) // 自定义日志类,继承 feign.Logger
// .logLevel(Logger.Level.BASIC)// 日志级别
// Default(long period, long maxPeriod, int maxAttempts)
.client(new Client.Default(null, null))// 默认 http
.retryer(new Retryer.Default(5000, 5000, 1))// 5s超时,仅1次重试
// .encoder(Encoder)
// .decoder(new StringDecoder())
.target(Target.EmptyTarget.create(someItfs.class));
// String url = "http://localhost:9104/";
//
System.out.println(someItfs1.getx(new URI(url)));
}
}

Rest api how to get parameters?

I'm new to rest api.
I need to make an api that takes a string as parameter and then return boolean.
Now my question is how to i pass that string to my api, and then get the string inside my api?
Here's one example that takes a string in parameter and has a default value if the query parameter is not provided:
#Path("business/department/")
public interface DepartmentService {
#GET
#Path("/cs/availability/chat")
#Produces(MediaType.APPLICATION_JSON)
boolean getCustomerServiceAvailability(#QueryParam("type") #DefaultValue("chat") String type);
}
and the implementation class can be anything that implements your interface. In this example, it's a stateless EJB
#Stateless
public class DepartmentServiceImpl implements DepartmentService {
#Context
private HttpServletRequest request;
private static final Logger LOGGER = Logger.getLogger(DepartmentServiceImpl.class.getName());
#Override
public boolean getCustomerServiceAvailability(String scheduleType) {
RequestInfo reqInfo = new RequestInfo(request, this.getClass(), "getCustomerServiceAvailability");
boolean available;
try {
available = CallBusinessService(scheduleType);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage());
throw new ServiceException();
} finally {
reqInfo.logExecutionTime();
}
}
}

Spring restful service test case fail HTTP status is 500

I want to implement test case for spring restful web services which return a json
MY controller test class is :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {WebAppContext.class,JpaTestConfiguration.class
})
#WebAppConfiguration
public class DominProfileRestControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testGetDomainProfile() throws Exception {
String profileId = domainProfile.getId().toString();
System.out.print("testing restful"+profileId);
mockMvc.perform(get("/service/domainprofile/get/{id}", profileId) )
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.city", is("Chandigrah")));
/* mockMvc.perform(get("/service/domainprofile/get/{id}",profileId).accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().contentType("text/plain;charset=ISO-8859-1"))
.andExpect(content().string("hello Prashant"));
*/
}
My Controller class Is :
#RestController
#RequestMapping("/service/domainprofile")
public class DominProfileRestController {
#Autowired
private JpaDomainProfileRepository jpaDomainProfileRepository;
#RequestMapping(value = "/get/{id}", method = RequestMethod.GET)
public DomainProfileResource getDomainProfile(#PathVariable String id) {
JpaDomainProfile domainProfile = jpaDomainProfileRepository.findOne(Long.valueOf(id));
DomainProfileResource domainProfileResource = new DomainProfileResource();
System.out.println("domainProfile.getCity()*************" + domainProfile.getCity());
System.out.println("domainProfile.getAddress()*************" + domainProfile.getAddress());
domainProfileResource.setCity(domainProfile.getCity());
domainProfileResource.setAddress(domainProfile.getAddress());
// return new ResponseEntity<DomainProfileResource>(domainProfileResource, HttpStatus.OK);
return domainProfileResource;
// return domainProfile;
}
}
When I run test case I got An error while we got values in domainprofile.city and domainprofile.address.
Error Is :
java.lang.AssertionError: Status
Expected :200
Actual :500
It Is Working Fine When I return a plain text
can you do this
mockMvc.perform(get("/service/domainprofile/get/{id}", profileId) )
.andDo(print());
this will print the full response with exception , now if you see HttpMessageNotWritableException which was the issue I was facing , you should try to serialize your object using jackson and see if it works (spring internally uses Jackson ). For example , If any of your fields are null the Serialization will fail.