Search code examples
javajbossresteasyjmockit

How can I inject an @Context-annotated field into a RestEasy @Provider?


With RestEasy in a JBoss container, I have an ExceptionMapper annotated with @Provider, which has access to the HttpServletRequest and HttpServletResponse via @Context annotations, like this:

@Provider
public class MyExceptionMapper implements ExceptionMapper<Throwable> {
  @Context
  private HttpServletRequest httpServletRequest;

  @Context
  private HttpServletResponse httpServletResponse;

  @Override
  public Response toResponse(final Throwable exception) {
    ...
    return response;
  }
}

I'm new to RestEasy, coming from a Spring background, so naively assumed I'd be able to inject those two fields and mock them in a unit test, but that seems harder than I'd expect!

In case the mocking framework is relevant, I'm using JMockit, and am also new to that. So far I've been able to apply my knowledge of Mockito to it with a good degree of success.

I'm not finding much by searching around the subject, other than lots of suggestions to run an embedded container in my unit tests. I'm not completely opposed to that, but it feels like overkill when I'm just trying to write a simple unit test.

I've tried several things in the test, most recently this:

public class MyExceptionMapperTest {
  @Injectable
  private HttpServletRequest httpServletRequest;

  @Injectable
  private HttpServletResponse httpServletResponse;

  @Tested
  private MyExceptionMapper exceptionMapper;

  @Test
  public void test() {
    exceptionMapper.toResponse(new Throwable());
  }
}

But this results in a NullPointerException in MyExceptionMapper the first time I refer to one of the @Context fields, which tells me that they are not being injected.

I've also tried:

  • using @Mocked instead of @Injectable;
  • directly instantiating MyExceptionMapper both with and without the @Tested annotation;
  • creating Expectations in my test; and
  • every permutation of the above

In all cases the @Context-annotated fields are null.

Hopefully I'm just missing something very obvious?


Solution

  • In the absence of a better answer, I've ended up using reflection in my tests to inject the value for these two properties. Not ideal, but it works:

    public class MyExceptionMapperTest {
      @Mocked
      private HttpServletRequest mockHttpServletRequest;
    
      @Mocked
      private HttpServletResponse mockHttpServletResponse;
    
      private MyExceptionMapper exceptionMapper = new MyExceptionMapper();
    
      @Before
      public void setup() {
        Field httpServletRequest = exceptionMapper.getClass().getDeclaredField("httpServletRequest");
        httpServletRequest.setAccessible(true);
        httpServletRequest.set(exceptionMapper, mockHttpServletRequest);
    
        Field httpServletResponse = exceptionMapper.getClass().getDeclaredField("httpServletResponse");
        httpServletResponse.setAccessible(true);
        httpServletResponse.set(exceptionMapper, mockHttpServletResponse);
      }
    }
    

    Then in my tests I can set expectations and verifications on the two @Mocked values as normal.

    I'm no expert on reflection, so this probably isn't the best/most efficient approach, but it's good enough for my needs.