unit test spring controller with WebTestClient and ControllerAdvice - junit

I'm trying to unit test my controller and the specific case which is : my service return a Mono.Empty, I throw a NotFoundException and I wan't to make sure I'm getting a 404 exception
here's my controller :
#GetMapping(path = "/{id}")
public Mono<MyObject<JsonNode>> getFragmentById(#PathVariable(value = "id") String id) throws NotFoundException {
return this.myService.getObject(id, JsonNode.class).switchIfEmpty(Mono.error(new NotFoundException()));
}
Here's my controller advice :
#ControllerAdvice
public class RestResponseEntityExceptionHandler {
#ExceptionHandler(value = { NotFoundException.class })
protected ResponseEntity<String> handleNotFound(SaveActionException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Resource not found");
}
}
and my test :
#Before
public void setup() {
client = WebTestClient.bindToController(new MyController()).controllerAdvice(new RestResponseEntityExceptionHandler()).build();
}
#Test
public void assert_404() throws Exception {
when(myService.getobject("id", JsonNode.class)).thenReturn(Mono.empty());
WebTestClient.ResponseSpec response = client.get().uri("/api/object/id").exchange();
response.expectStatus().isEqualTo(404);
}
I'm getting a NotFoundException But a 500 error not a 404 which mean my advice hasn't been called
stack trace :
java.lang.AssertionError: Status expected:<404> but was:<500>
> GET /api/fragments/idFragment
> WebTestClient-Request-Id: [1]
No content
< 500 Internal Server Error
< Content-Type: [application/json;charset=UTF-8]
Content not available yet
any idea ?

I believe you can delete this controller advice and just have the following:
#GetMapping(path = "/{id}")
public Mono<MyObject<JsonNode>> getFragmentById(#PathVariable(value = "id") String id) {
return this.myService.getObject(id, JsonNode.class)
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
}
As for ResponseEntityExceptionHandler, this class is part of Spring MVC so I don't think you should use it in a WebFlux application.

Related

How to handle timeout exception in quarkus?

I am trying to connect to a third-party API from quarkus controller . I have a controller using the method of service. The try catch block is not working.I have all the required dependency and i followed quarkus doc
Here is the code
Controller
package com.ncr.invoice;
// all imports over here
#Path("/api")
#RequestScoped
public class InvoiceController {
// all variable and injection
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Path("/invoice")
#Timeout(250)
#PermitAll
public Response getInvoice(AuthTokenRequestModel atrm){
SoupResponseInvoiceDetail srid = null;
try{
srid = service.getInvoice(
atrm.getMcn(),"transactionId", atrm.getInvoiceNumber()
);
LOG.info("try block end");
}
catch(InterruptedException e)
{
LOG.info("Over here");
return Response.status(401).build();
}
return Response.ok(srid).build();
}
return Response.status(401).build();
}
// all getter setter
}
service
package com.ncr.invoice;
//all imports
#Path("/")
#RegisterRestClient(configKey="invoice-api")
#ClientHeaderParam(name = "Authorization", value = "{basicAuth}")
public interface InvoiceService {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/")
public SoupResponseInvoiceDetail getInvoice(#QueryParam("id") String id,#QueryParam("txn_id") String txnId, #QueryParam("invoice") String invoice) throws InterruptedException;
default String basicAuth() {
String uName = ConfigProvider.getConfig().getValue("auth.username", String.class);
String pwd = ConfigProvider.getConfig().getValue("auth.pwd", String.class);
String creds = uName + ":" + pwd;
return "Basic " + Base64.getEncoder().encodeToString(creds.getBytes());
}
}
Error that i am getting
2021-01-07 13:07:42,286 INFO [com.ncr.inv.InvoiceController] (executor-thread-189) try block end
2021-01-07 13:07:42,555 ERROR [io.und.req.io] (executor-thread-189) Exception handling request 58a6d4b3-76c1-4a8b-b4a0-1e241219fb4d-4 to /api/invoice: org.jboss.resteasy.spi.UnhandledException: org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException: Timeout[com.ncr.invoice.InvoiceController#getInvoice] timed out
at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:218)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:519)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
.........................................
To me it look like you need to need to do something like this:
#Provider
public class TimeoutExceptionMapper implements ExceptionMapper {
private static final Logger LOGGER = LoggerFactory.getLogger(java.lang.invoke.MethodHandles.lookup().lookupClass());
#Override
public Response toResponse(TimeoutException exception) {
LOGGER.warn(exception.getMessage());
return getResponse();
}
public static Response getResponse() {
ErrorResponse response = new ErrorResponse();
response.setDescription("Operation timet out, try again later");
return status(Response.Status.GATEWAY_TIMEOUT).entity(response).build();
}
}

sending arraylist from a rest service

I am new to webservices and also REST. I am trying to send a message as a post request to a rest service using rest java client.I am trying to get response of previous requests also(everything in json format). So, am storing the message objects into an arraylist and sending the list as a reponse. But I am not able to get the previous messages. Please tell me if am doing anything wrong.
This is my message model class.
public class Messages {
private String id;
private String message;
public Messages() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
the following is my webservice to receive a message object and return a json array.
#Path("/json/messages")
public class JSONMessages {
public List<Messages> list = new ArrayList<Messages>();
List<Messages> getAllMessages(Messages m){
list.add(m);
return list;
}
#POST
#Path("/post")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response MessageListInJSON(Messages msg) {
System.out.println("message saved");
if(!(msg.getId().equals("1"))){
String output ="Invalid User";
return Response.ok(output).build();
}
else{
return Response.ok(getAllMessages(msg)).build();
}
}
}
Finally, the following is my client side code
public class ClientPost {
public static void main(String[] args) {
try {
ClientConfig clientConfig = new DefaultClientConfig();
Client client = Client.create(clientConfig);
WebResource webResource = client
.resource("http://localhost:8050/lab.rest.webservices/rest/json/messages/post");
//for(int i=0;i<5;i++){
String input = "{\"id\":\"1\", \"message\":\"hey there!\"}";
ClientResponse response = webResource.accept("application/json").type("application/json")
.entity(input).post(ClientResponse.class);
if (response.getStatus() !=200 ) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.getStatus());
}
System.out.println("Output from Server .... \n");
String output = response.getEntity(String.class);
System.out.println(output+"\n");
}
catch (Exception e) {
e.printStackTrace();
}
} }
Now, what I am expecting to see is the message I sent along with the previous responses stored in the array list(which were sent by running the client multiple times manually for now) but always am ending up with only the current message.
output:
Output from Server ....
[{"id":"1","message":"hey there!"}]
To be precise, what I want as output when i run my client several times(or put the try block in loop) is as follows which i am unable to get.
Output from Server ....
[{"id":"1","message":"hey there!"},{"id":"1","message":"hey there!"},{"id":"1","message":"hey there!"},{"id":"1","message":"hey there!"}] .
Resources in JAXRS aren't singletons. That means that for each request, the class JSONMessages is instantiated. So you lose the content of the attribute list. Changing it to static could fix your problem.
There is an annotation Singleton to change this behavior. In this case the resource will be managed as singleton and not in request scope. Here is a sample:
#Singleton
#Path("/json/messages")
public class JSONMessages {
(...)
}
Otherwise, be careful of concurrent accesses on your list. See this question for more details: java concurrent Array List access.
Hope it helps you,
Thierry

How to test the error output of a Spring Boot Controller?

I have implemented a simple controller with a simple request / response.
Controller
#RestController
#RequestMapping("/")
public class HelloWorldController extends AbstractRestController {
#RequestMapping(value = "/hello", method = RequestMethod.POST)
public HelloWorldResponse sayHello(#Valid #RequestBody HelloWorldRequest request) {
String message = String.format("Hello %s!", request.getSayHelloTo());
return new HelloWorldResponse(message);
}
}
Request
public class HelloWorldRequest {
#NotEmpty
#NotNull
private String sayHelloTo;
protected HelloWorldRequest() {
}
public HelloWorldRequest(String sayHelloTo) {
this.sayHelloTo = sayHelloTo;
}
public String getSayHelloTo() {
return sayHelloTo;
}
#Override
public String toString() {
return "HelloWorldRequest{" +
"sayHelloTo='" + sayHelloTo + '\'' +
'}';
}
}
When i want to test the correct output for the default error handling i seem to be unable to check the output of the default json format using a unit test. The response always seems to be empty. When i fire the request via a normal curl command is see a correct response. I assume this is because the returned JSON cannot be mapped on the HelloWorldResponse. Is there any way of checking if the returned output is valid on the response body?
Test
class TestSpec extends Specification {
MockMvc mockMvc
def setup() {
mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build()
}
def "Test simple action"() {
when:
def response = mockMvc.perform(post("/hello")
.contentType(MediaType.APPLICATION_JSON)
.content('{"sayHelloTo": ""}')
)
then:
response.andExpect(status().isOk())
}
}
Json Response
{
"timestamp" : 1426615606,
"exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
"status" : 400,
"error" : "Bad Request",
"path" : "/welcome",
"message" : "Required String parameter 'name' is not present"
}
try to debug with print() method, may be exception is thrown during execution.
MvcResult andReturn = mockMvc.perform(get("/api")
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andReturn();
The #Controller class you posted and the Test case (and response) don't seem to match (you're calling /hello in your test case and your #RequestMapping says /welcome ... Can we see the correct code? It looks like you have a #RequestParam("name") set, but you're not passing it.

WcfFacility and Sequence contains no elements error?

I have wcf library with service contracts and implementations.
[ServiceContract]
public interface IServiceProtoType
{
[OperationContract]
Response GetMessage(Request request);
[OperationContract]
String SayHello();
}
[DataContract]
public class Request
{
private string name;
[DataMember]
public string Name
{
get { return name; }
set { name = value; }
}
}
[DataContract]
public class Response
{
private string message;
[DataMember]
public string Message
{
get { return message; }
set { message = value; }
}
}
public class MyDemoService : IServiceProtoType
{
public Response GetMessage(Request request)
{
var response = new Response();
if (null == request)
{
response.Message = "Error!";
}
else
{
response.Message = "Hello, " + request.Name;
}
return response;
}
public string SayHello()
{
return "Hello, World!";
}
}
I have windows service project that references this library, where MyService is just an empty shell that inherits ServiceBase. This service is installed and running under local system.
static void Main()
{
ServiceBase.Run(CreateContainer().Resolve());
}
private static IWindsorContainer CreateContainer()
{
IWindsorContainer container = new WindsorContainer();
container.Install(FromAssembly.This());
return container;
}
public class ServiceInstaller : IWindsorInstaller
{
#region IWindsorInstaller Members
public void Install(IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
{
string myDir;
if (string.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath))
{
myDir = AppDomain.CurrentDomain.BaseDirectory;
}
else
{
myDir = AppDomain.CurrentDomain.RelativeSearchPath;
}
var wcfLibPath = Path.Combine(myDir , "WcfDemo.dll");
string baseUrl = "http://localhost:8731/DemoService/{0}";
AssemblyName myAssembly = AssemblyName.GetAssemblyName(wcfLibPath);
container
.Register(
AllTypes
.FromAssemblyNamed(myAssembly.Name)
.InSameNamespaceAs<WcfDemo.MyDemoService>()
.WithServiceDefaultInterfaces()
.Configure(c =>
c.Named(c.Implementation.Name)
.AsWcfService(
new DefaultServiceModel()
.AddEndpoints(WcfEndpoint
.BoundTo(new WSHttpBinding())
.At(string.Format(baseUrl,
c.Implementation.Name)
)))), Component.For<ServiceBase>().ImplementedBy<MyService>());
}
#endregion
}
In Client Console app I have the following code and I am getting the following error:
{"Sequence contains no elements"}
static void Main(string[] args)
{
IWindsorContainer container = new WindsorContainer();
string baseUrl = "http://localhost:8731/DemoService/{0}";
container.AddFacility<WcfFacility>(f => f.CloseTimeout = TimeSpan.Zero);
container
.Register(
Types
.FromAssemblyContaining<IServiceProtoType>()
.InSameNamespaceAs<IServiceProtoType>()
.Configure(
c => c.Named(c.Implementation.Name)
.AsWcfClient(new DefaultClientModel
{
Endpoint = WcfEndpoint
.BoundTo(new WSHttpBinding())
.At(string.Format(baseUrl,
c.Name.Substring(1)))
})));
var service1 = container.Resolve<IServiceProtoType>();
Console.WriteLine(service1.SayHello());
Console.ReadLine();
}
I have an idea what this may be but you can stop reading this now (and I apologize for wasting your time in advance) if the answer to the following is no:
Is one (or more) of Request, Response, or MyDemoService in the same namespace as IServiceProtoType?
I suspect that Windsor is getting confused about those, since you are doing...
Types
.FromAssemblyContaining<IServiceProtoType>()
.InSameNamespaceAs<IServiceProtoType>()
... and then configuring everything which that returns as a WCF client proxy. This means that it will be trying to create proxies for things that should not be and hence a Sequence Contains no Elements exception (not the most useful message IMHO but crushing on).
The simple fix would be just to put your IServiceProtoType into its own namespace (I often have a namespace like XXXX.Services for my service contracts).
If that is not acceptable to you then you need to work out another way to identify just the service contracts - take a look at the If method for example or just a good ol' Component.For perhaps.

Ehcache hangs in test

I am in the process of rewriting a bottle neck in the code of the project I am on, and in doing so I am creating a top level item that contains a self populating Ehcache. I am attempting to write a test to make sure that the basic call chain is established, but when the test executes it hands when retrieving the item from the cache.
Here are the Setup and the test, for reference mocking is being done with Mockito:
#Before
public void SetUp()
{
testCache = new Cache(getTestCacheConfiguration());
recordingFactory = new EntryCreationRecordingCache();
service = new Service<Request, Response>(testCache, recordingFactory);
}
#Test
public void retrievesResultsFromSuppliedCache()
{
ResultType resultType = mock(ResultType.class);
Response expectedResponse = mock(Response.class);
addToExpectedResults(resultType, expectedResponse);
Request request = mock(Request.class);
when(request.getResultType()).thenReturn(resultType);
assertThat(service.getResponse(request), sameInstance(expectedResponse));
assertTrue(recordingFactory.requestList.contains(request));
}
private void addToExpectedResults(ResultType resultType,
Response response) {
recordingFactory.responseMap.put(resultType, response);
}
private CacheConfiguration getTestCacheConfiguration() {
CacheConfiguration cacheConfiguration = new CacheConfiguration("TEST_CACHE", 10);
cacheConfiguration.setLoggingEnabled(false);
return cacheConfiguration;
}
private class EntryCreationRecordingCache extends ResponseFactory{
public final Map<ResultType, Response> responseMap = new ConcurrentHashMap<ResultType, Response>();
public final List<Request> requestList = new ArrayList<Request>();
#Override
protected Map<ResultType, Response> generateResponse(Request request) {
requestList.add(request);
return responseMap;
}
}
Here is the ServiceClass
public class Service<K extends Request, V extends Response> {
private Ehcache cache;
public Service(Ehcache cache, ResponseFactory factory) {
this.cache = new SelfPopulatingCache(cache, factory);
}
#SuppressWarnings("unchecked")
public V getResponse(K request)
{
ResultType resultType = request.getResultType();
Element cacheEntry = cache.get(request);
V response = null;
if(cacheEntry != null){
Map<ResultType, Response> resultTypeMap = (Map<ResultType, Response>) cacheEntry.getValue();
try{
response = (V) resultTypeMap.get(resultType);
}catch(NullPointerException e){
throw new RuntimeException("Result type not found for Result Type: " + resultType);
}catch(ClassCastException e){
throw new RuntimeException("Incorrect Response Type for Result Type: " + resultType);
}
}
return response;
}
}
And here is the ResponseFactory:
public abstract class ResponseFactory implements CacheEntryFactory{
#Override
public final Object createEntry(Object request) throws Exception {
return generateResponse((Request)request);
}
protected abstract Map<ResultType,Response> generateResponse(Request request);
}
After wrestling with it for a while, I discovered that the cache wasn't being initialized. Creating a CacheManager and adding the cache to it resolved the problem.
I also had a problem with EHCache hanging, although only in a hello-world example. Adding this to the end fixed it (the application ends normally).
CacheManager.getInstance().removeAllCaches();
https://stackoverflow.com/a/20731502/2736496