javamavenjettyjersey-2.0jersey-test-framework

ExceptionMapper not working in JerseyTest framework


I had made an RESTFUL-API for an specific object and using javax.validation.Constraints for validation, when I execute the app it works as expected and ExceptionMapper class is been executed correctly but when I try to automatize tests with JerseyFramework the ExceptionMapper is not executed or handled. So it works in app but not when i test it.

Main Class that launch app

package com.carlos.zerga.server; 
public class App  {
    public static void main( String[] args ) throws Exception {

        AppResourceConfig config = new AppResourceConfig();
        ServletHolder jerseyServlet
                = new ServletHolder(new ServletContainer(config));

        Server server = new Server(8081);
        ServletContextHandler context
                = new ServletContextHandler(server, "/");

        context.addServlet(jerseyServlet, "/*");

        server.start();
        server.join();
   } 
}

ResourceConfig for App

package com.carlos.zerga.server;
public class AppResourceConfig extends ResourceConfig{

    public AppResourceConfig() {
        register(new PropertiesBinder());
        register(EntityValidationExceptionMapper.class);
       packages(true,"com.carlos.zerga.properties");
    }
}

ExceptionMapper for ValidationExceptions

package com.carlos.zerga.properties.presentation.handler;
@Provider
public class EntityValidationExceptionMapper implements    
ExceptionMapper<BeanValidationException> {

    public Response toResponse(BeanValidationException e) {

        return Response.status(200).
                type(MediaType.APPLICATION_JSON_TYPE).
                entity(buildResponse((ConstraintViolationException) e.getInternalException())).
                build();
    }
    private ExceptionResponse buildResponse(ConstraintViolationException e)
    {
        ArrayList<String> messages = new ArrayList<String>();
        for(ConstraintViolation constraintViolation:e.getConstraintViolations())
        {
             messages.add(constraintViolation.getMessage());
        }
        return new ExceptionResponse(200, messages,e.getClass().getSimpleName());
    }

}

Resouce Class

package com.carlos.zerga.properties.presentation;
@Path("property")
public class PropertiesApiResource {

    @POST
    @Path("new")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Property createProperty(Property property)
    {
        return property;
    }
    //LOT OF OTHER ROUTES
    ......
}

Test Case

package com.carlos.zerga.test.properties.presentation;

@RunWith(DataProviderRunner.class)
public class PropertiesApiResourceTest extends JerseyTest {

    @Override
    protected Application configure() {

        return new ResourceConfig()
            .register(new PropertiesBinder())
            .register(new EntityValidationExceptionMapper())
            .register(new ConstraintValidationException())
            .packages(true,"com.carlos.zerga.properties");
     }
     //Here comes the error
     @Test
     @UseDataProvider("provideInvalidPropertiesPSR_PA")
     public void testInValidPropertyCreation(Property property)
     {
        Response response=target("property/new").request(MediaType.APPLICATION_JSON).method("POST",Entity.json(property));
        assertEquals(response.getMediaType(),MediaType.APPLICATION_JSON_TYPE);
        String exceptionResponse=response.readEntity(String.class);
        System.out.print(exceptionResponse);
    }

POM.xml

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
        <version>${jersey.version}</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet-core</artifactId>
        <version>${jersey.version}</version>
    </dependency>

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-server</artifactId>
        <version>${jetty.version}</version>
    </dependency>

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-servlet</artifactId>
        <version>${jetty.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>${jersey.version}</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.hk2</groupId>
        <artifactId>hk2-metadata-generator</artifactId>
        <version>2.4.0</version>
    </dependency>
    <dependency>
        <groupId>com.tngtech.java</groupId>
        <artifactId>junit-dataprovider</artifactId>
        <version>1.12.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.test-framework</groupId>
        <artifactId>jersey-test-framework-core</artifactId>
        <version>${jersey.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-jetty</artifactId>
        <version>${jersey.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.ext</groupId>
        <artifactId>jersey-bean-validation</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.connectors</groupId>
        <artifactId>jersey-jetty-connector</artifactId>
        <version>${jersey.version}</version>
        <scope>test</scope>
    </dependency>


</dependencies>
<properties>
    <jersey.version>2.26-b03</jersey.version>
    <jetty.version>9.2.14.v20151106</jetty.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

When I send by postman it returns me what I want:

{"errorCode":200,"errors":["Bathroom : Amount bathrooms can't be empty"],"errorDescription":"ConstraintViolationException"}

But when I run the tests returns me:

    javax.ws.rs.ProcessingException:
    Exception Description: Constraints violated on marshalled bean:
    com.carlos.zerga.properties.domain.entity.Property@3caeaf62
    -->Violated constraint on property bathRooms: "Bathroom : Minimun amout of bathrooms is 3".
    -->Violated constraint on property bedRooms: "Bathroom : Minimun amout of bathrooms is 3".
    Internal Exception: javax.validation.ConstraintViolationException

    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:262)
    at org.glassfish.jersey.client.JerseyInvocation$1.call(JerseyInvocation.java:759)
    at org.glassfish.jersey.client.JerseyInvocation$1.call(JerseyInvocation.java:756)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:228)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:407)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:756)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:443)
    at com.carlos.zerga.test.properties.presentation.PropertiesApiResourceTest.testInValidPropertyCreation(PropertiesApiResourceTest.java:93)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at com.tngtech.java.junit.dataprovider.DataProviderFrameworkMethod.invokeExplosively(DataProviderFrameworkMethod.java:77)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
    Caused by: Exception [EclipseLink-7510] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.BeanValidationException
    Exception Description: Constraints violated on marshalled bean:
    com.carlos.zerga.properties.domain.entity.Property@3caeaf62
    -->Violated constraint on property bathRooms: "Bathroom : Minimun amout of bathrooms is 3".
    -->Violated constraint on property bedRooms: "Bathroom : Minimun amout of bathrooms is 3".
    Internal Exception: javax.validation.ConstraintViolationException
    at org.eclipse.persistence.exceptions.BeanValidationException.constraintViolation(BeanValidationException.java:53)
    at org.eclipse.persistence.jaxb.JAXBBeanValidator.buildConstraintViolationException(JAXBBeanValidator.java:391)
    at org.eclipse.persistence.jaxb.JAXBBeanValidator.validate(JAXBBeanValidator.java:275)
    at org.eclipse.persistence.jaxb.JAXBMarshaller.validateAndTransformIfNeeded(JAXBMarshaller.java:604)
    at org.eclipse.persistence.jaxb.JAXBMarshaller.marshal(JAXBMarshaller.java:494)
    at org.eclipse.persistence.jaxb.rs.MOXyJsonProvider.writeTo(MOXyJsonProvider.java:957)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:265)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:250)
    at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
    at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1126)
    at org.glassfish.jersey.client.ClientRequest.doWriteEntity(ClientRequest.java:517)
    at org.glassfish.jersey.client.ClientRequest.writeEntity(ClientRequest.java:499)
    at org.glassfish.jersey.client.internal.HttpUrlConnector._apply(HttpUrlConnector.java:393)
    at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:285)
    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:253)
    ... 34 more
    Caused by: javax.validation.ConstraintViolationException
    at org.eclipse.persistence.jaxb.JAXBBeanValidator.buildConstraintViolationException(JAXBBeanValidator.java:389)
    ... 47 more

Solution

  • If you look carefully at the stacktrace, you will see that this error is actually happening on the client side, even before the request gets to the server. The reason is that you are using MOXy, which uses JAXB, and the JAXB bean validation is enabled by default. You can see in the stacktrace that the JAXBBeanValidator is being envoked, and that it is happening on the client side marshalling of the entity.

    There are a couple solutions:

    Configure MOXy to disable bean validation

    To configure MOXy, you can use a MoxyJsonConfig. You should actually do this on the server side also so that you don't get double validation on the server. The bean validation provided by Jersey is different from that provided by MOXy.

    In Test.java

    @Override
    public void configureClient(ClientConfig config) {
        config.register(new MoxyJsonConfig().setFormattedOutput(true)
                .property(MarshallerProperties.BEAN_VALIDATION_MODE, BeanValidationMode.NONE)
                .resolver());
    }
    

    The configureClient method is a method on the JerseyTest that you can override to configure the Client. You can register the same MoxyJsonConfig with the ResourceConfig also for the server side.

    Don't use MOXy

    My solution would be to just get rid of MOXy, and use Jackson instead. Just change the following dependency to the Jackson on below it

    <!-- remove this -->
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <!-- use this -->
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    

    And also add @Valid tag to:

    Resource.java

    ...... MORE ROUTES
    @POST
    @Path("new")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Property createProperty(@Valid Property property){
        return property;
    }
    ...... MORE ROUTES
    

    You will not have this problem with Jackson as Jackson doesn't do any bean validation. Aside from this problem, my personal opinion is that Jackson is just a much better overall library for JSON anyway.

    Note: If you turn off the MOXy validation (whether through configuration or by using Jackson), you would no longer be trying to catch a BeanValidationException (which is a MOXy) class, but instead you can directly catch ConstraintViolationException.