4

Trying to test a fairly simple JAX-RS endpoint

@ApplicationScoped
@Path("mypath")
public class MyRestService {
    @Inject
    private Logger logger;

    @Inject
    private EjbService ejbService;

    @GET
    public String myMethod() {
        logger.info("...");
        return ejbService.myMethod();
    }
}

with Mockito and Jersey Test

@RunWith(MockitoJUnitRunner.class)
public class MyRestServiceTest extends JerseyTest {
    @Mock
    private EjbService ejbService;

    @Mock
    private Logger logger;

    @InjectMocks
    private MyRestService myRestService;

    ...

    @Override
    protected Application configure() {
        MockitoAnnotations.initMocks(this);
        return new ResourceConfig().register(myRestService);
    }
}

The Grizzly container is returning a org.glassfish.hk2.api.UnsatisfiedDependencyException for Logger and EjbService even thought the dependencies are injected correctly by Mockito.

Seems Grizzly is trying, correctly, to ovverride the Mockito mocks. If I register an AbstractBinder in the configure method, everything works fine.

.register(new AbstractBinder() {
    @Override
    protected void configure() {
        bind(ejbService).to(EjbService.class);
        bind(logger).to(Logger.class);
    }
});

But I don't feel it's the best way to accomplish injection. Mockito style is better imho. What do I need to do to solve this issue?

LppEdd
  • 20,274
  • 11
  • 84
  • 139
  • Have you solved it the way you wanted eventually? I'm, thinking perhaps to use reflection to investigate the test class, and for every `@Mock` annotated property perform the bind. – Nom1fan Jan 02 '22 at 11:27
  • @Nom1fan as far as I remember I had to keep the manual binding configuration. I had already put too much time on it. – LppEdd Jan 02 '22 at 11:30
  • OK, thanks. I was able to do as I said above and it works well, in case someone reading this is interested feel free to reach out and I will provide the code. – Nom1fan Jan 02 '22 at 14:21
  • 1
    @Nom1fan maybe post it as an answer, it's more accessible – LppEdd Jan 02 '22 at 14:22

2 Answers2

1

I was able to create the following base class in order to achieve integration between JerseyTest and Mockito such as the OP aimed for:

package org.itest;

import com.google.common.collect.Maps;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.test.JerseyTestNg;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.util.ReflectionUtils;

import javax.ws.rs.core.Application;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Nom1fan
 */
public abstract class JerseyTestBase extends JerseyTestNg.ContainerPerClassTest {

    @Override
    protected Application configure() {
        MockitoAnnotations.openMocks(this);
        ResourceConfig application = new ResourceConfig();
        Object resourceUnderTest = getResourceUnderTest();
        application.register(resourceUnderTest);

        Map<String, Object> properties = Maps.newHashMap();
        properties.put(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
        properties.put("contextConfigLocation", "classpath:applicationContext.xml");

// Retrieve the fields annotated on subclass as @Mock via reflection and keep each instance 
// and its type on an entry in the map, later used to bind to Jersey infra.
        HashMap<Object, Class<?>> mocksToBindMap = Maps.newHashMap();
        List<Field> fieldsWithMockAnnotation = FieldUtils.getFieldsListWithAnnotation(getClass(), Mock.class);
        for (Field declaredField : fieldsWithMockAnnotation) {
            declaredField.setAccessible(true);
            Object fieldObj = ReflectionUtils.getField(declaredField, this);
            mocksToBindMap.put(fieldObj, declaredField.getType());
        }

        application.setProperties(properties);
        application.register(new AbstractBinder() {
            @Override
            protected void configure() {
                for (Map.Entry<Object, Class<?>> mockToBind : mocksToBindMap.entrySet()) {
                    bind(mockToBind.getKey()).to(mockToBind.getValue());
                }
            }
        });
        return application;
    }

    protected abstract Object getResourceUnderTest();
}

The hook getResourceUnderTest must be implemented by the extending test class, providing the instance of the resource it wishes to test.

Test class example:

import org.itest.JerseyTestBase;
import org.mockito.InjectMocks;
import org.mockito.Mock;

public class MyJerseyTest extends JerseyTestBase {
    
    @Mock
    private MockA mockA;
    @Mock
    private MockB mockB;
    @InjectMocks
    private MyResource myResource;


    @Override
    protected Object getResourceUnderTest() {
        return myResource;
    }


    @Test
    public void myTest() {
        when(mockA.foo()).thenReturn("Don't you dare go hollow");
        when(mockB.bar()).thenReturn("Praise the Sun \\[T]/");

        // Test stuff
        target("url...").request()...
    }
}

MyResource class looks something like this:

@Path("url...")
@Controller
public class MyResource {
   
    private final MockA mockA;
    private final MockB mockB;

   @Autowired        // Mocks should get injected here
   public MyResource(MockA mockA, MockB mockB) {
      this.mockA = mockA; 
      this.mockB = mockB;
   }

   @GET
   public Response someAPI() {
     mockA.foo(); 
     mockB.bar();
   }
}

NOTE: I used Spring's and Apache's reflection utils to make things easier but it's not mandatory. Simple reflection code which can be written by hand.

Nom1fan
  • 846
  • 2
  • 11
  • 27
  • I actually trusted @LppEdd regarding what he experienced and didn't test it myself at first, but just out of curiosity I commented out my reflection code and did not get `org.glassfish.hk2.api.UnsatisfiedDependencyException`. It simply works for me even without this code. Maybe something changed along the versions, but it seems very simple to use Mockito with JerseyTest now. Just add `@Mock` and `@InjectMocks` on your resource and call `MockitoAnnotations.openMocks(this);` on your test class. That's it. – Nom1fan Jan 03 '22 at 08:01
0

The MockitoJUnitRunner is for unit tests and JerseyTest is for integration tests.

When using Mockito, your tests will call directly the declared myRestService and Mockito dependency injection will take place.

When using JerseyTest, a new web container is created and your tests talk to MyRestService via an HTTP call. Inside this container, the real dependency injection is happening, the classes are not even seeing you declared mocks.

You can use JerseyTest and Mockito together, exactly as you did. It just requires some extra configurations (as you already found) and the @RunWith annotation is not necessary.

Alex Oliveira
  • 893
  • 1
  • 9
  • 27
  • Thanks. The thing is the AbstractBinder is not that flexible and will not work, standard, with annotations it doesn't recognize. – LppEdd Jun 30 '18 at 14:41