带有 Jersey 测试和 JAX-RS 的 Mockito - UnsatisfiedDependencyException
Mockito with Jersey Test and JAX-RS - UnsatisfiedDependencyException
尝试测试一个相当简单的 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 容器正在为 Logger
和 EjbService
返回 org.glassfish.hk2.api.UnsatisfiedDependencyException
,甚至认为 Mockito 正确注入了依赖项。
似乎 Grizzly 正在尝试正确地覆盖 Mockito 模拟。
如果我在 configure
方法中注册一个 AbstractBinder
,一切正常。
.register(new AbstractBinder() {
@Override
protected void configure() {
bind(ejbService).to(EjbService.class);
bind(logger).to(Logger.class);
}
});
但我觉得这不是完成注入的最佳方式。恕我直言,Mockito 风格更好。
我需要做什么来解决这个问题?
MockitoJUnitRunner
用于单元测试,JerseyTest
用于集成测试。
使用 Mockito 时,您的测试将直接调用声明的 myRestService
并且 Mockito 依赖注入将发生。
使用 JerseyTest 时,会创建一个新的 Web 容器,并且您的测试会通过 HTTP 调用与 MyRestService
通信。在这个容器中,真正的依赖注入正在发生,类 甚至没有看到你声明的模拟。
您可以像您一样一起使用 JerseyTest 和 Mockito。它只需要一些额外的配置(正如您已经发现的那样)并且不需要 @RunWith
注释。
我能够创建以下基础 class 以实现 JerseyTest
和 Mockito
之间的集成,例如 OP 旨在:
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
必须由扩展测试 class 实现,提供它希望测试的资源实例。
测试class例子:
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 看起来像这样:
@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 的反射实用程序使事情变得更容易,但这不是强制性的。可以手写的简单反射代码
尝试测试一个相当简单的 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 容器正在为 Logger
和 EjbService
返回 org.glassfish.hk2.api.UnsatisfiedDependencyException
,甚至认为 Mockito 正确注入了依赖项。
似乎 Grizzly 正在尝试正确地覆盖 Mockito 模拟。
如果我在 configure
方法中注册一个 AbstractBinder
,一切正常。
.register(new AbstractBinder() {
@Override
protected void configure() {
bind(ejbService).to(EjbService.class);
bind(logger).to(Logger.class);
}
});
但我觉得这不是完成注入的最佳方式。恕我直言,Mockito 风格更好。 我需要做什么来解决这个问题?
MockitoJUnitRunner
用于单元测试,JerseyTest
用于集成测试。
使用 Mockito 时,您的测试将直接调用声明的 myRestService
并且 Mockito 依赖注入将发生。
使用 JerseyTest 时,会创建一个新的 Web 容器,并且您的测试会通过 HTTP 调用与 MyRestService
通信。在这个容器中,真正的依赖注入正在发生,类 甚至没有看到你声明的模拟。
您可以像您一样一起使用 JerseyTest 和 Mockito。它只需要一些额外的配置(正如您已经发现的那样)并且不需要 @RunWith
注释。
我能够创建以下基础 class 以实现 JerseyTest
和 Mockito
之间的集成,例如 OP 旨在:
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
必须由扩展测试 class 实现,提供它希望测试的资源实例。
测试class例子:
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 看起来像这样:
@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 的反射实用程序使事情变得更容易,但这不是强制性的。可以手写的简单反射代码