EJB 无接口视图测试(arquillain 和 mockito)
EJB no interface view testing (arquillain & mockito)
我正在开发 Java EE 7(在 wildfly 9.0.2 上)应用程序,我偶然发现了一篇文章 http://www.oracle.com/technetwork/articles/java/intondemand-1444614.html。主要关于:
Premature Extensibility Is the Root of Some Evil
这在我遇到的某些情况下是有道理的。我已经将一些 classes 更改为无界面视图。实现本身不是问题,但是测试是问题。
例如我有这 2 classes.
@Stateless
public class SomeBean {
public String getText()
{
return "Test text";
}
}
和
@Stateless
public class SomeOtherBean {
@Inject
private SomeBean someBean;
public String getText()
{
return someBean.getText();
}
}
我希望 someBean 属性 最好被模拟对象覆盖。不改变 SomeBean
和 SomeOtherBean
class。我已经尝试了一些例子,但它们没有用,例如:
https://github.com/arquillian/arquillian-showcase/tree/master/extensions/autodiscover/src/test/java/org/jboss/arquillian/showcase/extension/autodiscover
有没有人遇到过这个问题并有解决办法?
我最终使用了 2 个解决方案。
解决方案 1:使用 mockito 进行内部或较小的测试
对于测试特定的 class Mockito 非常有用,因为它支持依赖注入。
@RunWith(MockitoJUnitRunner.class)
public class SomeOtherBeanTest {
@Mock
private SomeBean someBean;
@InjectMocks
private SomeOtherBean someOhterBean;
@Before
public void setUp() {
Mockito.when(someBean.getText()).thenReturn("Overwritten!");
}
@Test
public void testGetText() throws Exception {
assertEquals("Overwritten!", someOhterBean.getText());
}
}
解决方案 2:使用@Produces 和@Alternatives 模拟外部服务(例如模拟 OAuth2 服务器)或更大的测试(例如集成测试)
首先我创建一个新的 @Alternative
注释:
@Alternative
@Stereotype
@Retention(RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public @interface CDIMock {}
然后将其作为原型添加到 arquillian beans.xml
部署中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
<alternatives>
<stereotype>com.project.CDIMock</stereotype>
</alternatives>
</beans>
之后在单独的 class:
中创建一个新的 @Producer
方法
public class SomeBeanMockProducer {
@Produces @CDIMock
public static SomeBean produce() {
SomeBean someBean = Mockito.mock(SomeBean.class);
Mockito.when(someBean.getText()).thenReturn("mocked");
return someBean;
}
}
将 SomeBeanMockProducer
class 添加到 arquillian 部署中,您应该可以正常使用它。
此解决方案的替代方法是使用 @Specializes
并扩展 SomeBean 实现。在我看来,这并没有像 @Alternative
+ Mocking 解决方案(在我的示例中为 @CDIMock
)那样给我足够的控制权。
例如,假设我 SomeBean
有调用远程服务器的方法。如果我向它添加一个方法而忘记在 @Specializes
class 中 @override
this 它将进行真正的远程调用,Mocking 不会出现这种情况。
很明显,用 Mock 或其他专用对象替换注入的无接口 class 更加困难,因为这正是您不为 bean 声明接口所需要的。
话虽如此,如果您不是 运行 CDI 容器(例如,进行 POJO 单元测试),我认为使用 Mockito 是最简单的方法。
如果您想在容器中以 "pure CDI" 方式进行操作,您可以使用 CDI 的替代和专业化机制,如 Java EE 6 tutorial.
中所述
@Specializes
public class SomeBeanMock extends SomeBean {
@Overrides
public String getText()
{
return "mock";
}
}
当然你只能使用子class原始bean的模拟(因为你没有接口)并且你受限于通常的可见性规则。 Changing/mocking 私有字段或方法需要反射或字节码操作(这是 Mockito 在幕后所做的)。
我正在开发 Java EE 7(在 wildfly 9.0.2 上)应用程序,我偶然发现了一篇文章 http://www.oracle.com/technetwork/articles/java/intondemand-1444614.html。主要关于:
Premature Extensibility Is the Root of Some Evil
这在我遇到的某些情况下是有道理的。我已经将一些 classes 更改为无界面视图。实现本身不是问题,但是测试是问题。
例如我有这 2 classes.
@Stateless
public class SomeBean {
public String getText()
{
return "Test text";
}
}
和
@Stateless
public class SomeOtherBean {
@Inject
private SomeBean someBean;
public String getText()
{
return someBean.getText();
}
}
我希望 someBean 属性 最好被模拟对象覆盖。不改变 SomeBean
和 SomeOtherBean
class。我已经尝试了一些例子,但它们没有用,例如:
https://github.com/arquillian/arquillian-showcase/tree/master/extensions/autodiscover/src/test/java/org/jboss/arquillian/showcase/extension/autodiscover
有没有人遇到过这个问题并有解决办法?
我最终使用了 2 个解决方案。
解决方案 1:使用 mockito 进行内部或较小的测试
对于测试特定的 class Mockito 非常有用,因为它支持依赖注入。
@RunWith(MockitoJUnitRunner.class)
public class SomeOtherBeanTest {
@Mock
private SomeBean someBean;
@InjectMocks
private SomeOtherBean someOhterBean;
@Before
public void setUp() {
Mockito.when(someBean.getText()).thenReturn("Overwritten!");
}
@Test
public void testGetText() throws Exception {
assertEquals("Overwritten!", someOhterBean.getText());
}
}
解决方案 2:使用@Produces 和@Alternatives 模拟外部服务(例如模拟 OAuth2 服务器)或更大的测试(例如集成测试)
首先我创建一个新的 @Alternative
注释:
@Alternative
@Stereotype
@Retention(RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public @interface CDIMock {}
然后将其作为原型添加到 arquillian beans.xml
部署中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
<alternatives>
<stereotype>com.project.CDIMock</stereotype>
</alternatives>
</beans>
之后在单独的 class:
中创建一个新的@Producer
方法
public class SomeBeanMockProducer {
@Produces @CDIMock
public static SomeBean produce() {
SomeBean someBean = Mockito.mock(SomeBean.class);
Mockito.when(someBean.getText()).thenReturn("mocked");
return someBean;
}
}
将 SomeBeanMockProducer
class 添加到 arquillian 部署中,您应该可以正常使用它。
此解决方案的替代方法是使用 @Specializes
并扩展 SomeBean 实现。在我看来,这并没有像 @Alternative
+ Mocking 解决方案(在我的示例中为 @CDIMock
)那样给我足够的控制权。
例如,假设我 SomeBean
有调用远程服务器的方法。如果我向它添加一个方法而忘记在 @Specializes
class 中 @override
this 它将进行真正的远程调用,Mocking 不会出现这种情况。
很明显,用 Mock 或其他专用对象替换注入的无接口 class 更加困难,因为这正是您不为 bean 声明接口所需要的。
话虽如此,如果您不是 运行 CDI 容器(例如,进行 POJO 单元测试),我认为使用 Mockito 是最简单的方法。
如果您想在容器中以 "pure CDI" 方式进行操作,您可以使用 CDI 的替代和专业化机制,如 Java EE 6 tutorial.
中所述@Specializes
public class SomeBeanMock extends SomeBean {
@Overrides
public String getText()
{
return "mock";
}
}
当然你只能使用子class原始bean的模拟(因为你没有接口)并且你受限于通常的可见性规则。 Changing/mocking 私有字段或方法需要反射或字节码操作(这是 Mockito 在幕后所做的)。