4

尝试测试一个相当简单的 JAX-RS 端点

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

    @Inject
    private EjbService ejbService;

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

使用 Mockito 和 Jersey 测试

@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);
    }
}

Grizzly 容器正在返回一个org.glassfish.hk2.api.UnsatisfiedDependencyExceptionfor LoggerEjbService甚至认为 Mockito 正确注入了依赖项。

似乎灰熊正试图正确地超越 Mockito 模拟。如果我AbstractBinderconfigure方法中注册一个,一切正常。

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

但我不认为这是完成注射的最佳方式。Mockito 风格更好恕我直言。我需要做什么来解决这个问题?

4

2 回答 2

1

我能够创建以下基类,以实现 OP 之间的集成JerseyTestMockito例如:

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();
}

钩子getResourceUnderTest必须由扩展测试类实现,提供它希望测试的资源实例。

测试类示例:

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 类看起来像这样:

@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();
   }
}

注意:我使用 Spring 和 Apache 的反射工具使事情变得更容易,但这不是强制性的。可以手写的简单反射代码。

于 2022-01-02T17:18:28.037 回答
0

MockitoJUnitRunner用于单元测试和集成JerseyTest测试。

使用 Mockito 时,您的测试将直接调用已声明的myRestServiceMockito 依赖注入。

使用 JerseyTest 时,会创建一个新的 Web 容器,并MyRestService通过 HTTP 调用与您的测试对话。在这个容器中,真正的依赖注入正在发生,这些类甚至没有看到你声明的模拟。

您可以像以前一样同时使用 JerseyTest 和 Mockito。它只需要一些额外的配置(正如您已经发现的那样),并且@RunWith不需要注释。

于 2018-06-30T14:27:36.590 回答