如何使用 Arquillian 模拟服务?

发布于 2024-10-19 18:33:59 字数 841 浏览 8 评论 0原文

是否可以在 Arquillian 中使用某种模拟框架,或者确切地说如何模拟注入的 EJB?我知道,通过使用 CDI(上下文和依赖注入),可以在测试中注入替代方案。但是如果没有 CDI 作为注入机制,当我只使用 EJB 注入时,这怎么可能?

最近,我使用服务接口模拟实现测试了我的 EJB,如下所示:

// Service inteface 
public interface Audit {
   void audit(String info);
}

// Mock implementation
@Stateless
public class MockAuditBean implements Audit {

    public static String lastInfo = null;

    @Override
    public void audit(String info) {
        this.lastInfo = info;
    }
}

// assert in test
assertTrue(MockAuditBean.lastInfo.contains("dummy"));

这种方法是可能的,但需要大量自定义模拟实现。更糟糕的是,注入的模拟实例是代理并使用服务接口。这些不能转换为模拟实现类来比较结果。只能使用模拟实现的静态成员和方法。

我还测试了另一种手动设置相关 EJB 的可能性。这种方法有几个缺点。它要求测试的目标 EJB 具有非私有成员或设置器。当目标 EJB 依赖 @PostConstruct 生命周期注释时,您必须在手动“注入”设置后调用它。 该解决方案的优点是能够使用模拟框架,例如mockito或jMock。

有人可以分享经验,如何测试和设置此类集成测试,甚至在其中使用模拟框架?

Is it possible to use some kind of mocking framework with Arquillian, or precisely how to mock injected EJBs? I know that, with using the CDI (Contexts and Dependency Injection), it is possible to inject alternatives in test. But without CDI as injection mechanism, when I'm only using EJB injection, how this is possible?

Recently I have tested my EJBs with service interface mock implementation as following:

// Service inteface 
public interface Audit {
   void audit(String info);
}

// Mock implementation
@Stateless
public class MockAuditBean implements Audit {

    public static String lastInfo = null;

    @Override
    public void audit(String info) {
        this.lastInfo = info;
    }
}

// assert in test
assertTrue(MockAuditBean.lastInfo.contains("dummy"));

This approach is possible but requires a lot of custom mock implementations. What is worse, injected instances of mocks are proxies and uses service interface. These can not be cast to mock implementation class to compare results. Only static members and methods of mock implementation can be used.

I have tested also another possibilities to set related EJBs manually. This approach has several draw-backs. It requires that target EJB of test has non-private members or setters for them. When target EJB relies on @PostConstruct lifecycle annotation, you have to call it after your manual "injection" setting.
Advantage of this solution is the ability to use mock frameworks, like mockito or jMock.

Have someone an experience to share, how to test and set-up such integration test, or even use mocking frameworks in it ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

过潦 2024-10-26 18:33:59

IMO,EJB 的设计并没有考虑到测试。你的替代方案听起来是一个足够好的妥协方案,我会选择它。使用mockito 是一个主要优点,即使在使用CDI 时我也会使用它。

我将使用“默认”成员范围和 javadoc 来让其他开发人员仅出于测试目的访问它们。

IMO, EJBs where not designed with testing in mind. Your alternative sounds like a good enough compromise and I'd go for it. Using mockito is a major plus and I use it even when working with CDI.

I'd use the "default" member scope and javadoc to other developers access them for testing purposes only.

紫瑟鸿黎 2024-10-26 18:33:59

Oracle 的这篇文章展示了一种使用 JUnit 和 Mockito“注入”EJB 进行测试的方法:
http://www.oracle.com/technetwork/articles/java/unittesting -455385.html

编辑:
基本上,Mockito 的包含允许模拟 EntityManager 等对象:

import static org.mockito.Mockito.*;

...

em = mock(EntityManager.class);

它们展示了 EJB 的方法以及使用mockito。给定一个 EJB:

@Stateless
public class MyResource {
 @Inject
 Instance<Consultant> company;

 @Inject
 Event<Result> eventListener;

测试可以“注入”这些对象:

public class MyResourceTest {

 private MyResource myr;
 @Before
 public void initializeDependencies(){
 this.myr = new MyResource();
 this.myr.company = mock(Instance.class);
 this.myr.eventListener = mock(Event.class);
 }

请注意,MyResource 和 MyResource 位于同一类路径中,但位于不同的源文件夹中,因此您的测试可以访问受保护的字段 company事件监听器


编辑:

注意:您可以使用 JBoss 的 FacesMockitoRunner (https://community.jboss .org/thread/170800)为常见的 JSF 组件完成此操作,并为其他组件使用注释(启用 CDI 的 Java EE 6 作为此操作的先决条件,但不需要 JBoss 服务器):

  1. < p>在 maven 中包含 jsf、mockito 和 jsf-mockito 依赖项:

     <依赖项>
            junit
            junit;
            <版本>4.11
            <范围>测试
        
        <依赖关系>
            org.mockito;
            mockito-core;  
            <版本>1.9.5 
            <范围>测试
        
        <依赖关系>
          org.jboss.test-jsf;
          jsf-mockito;
          <版本>1.1.7-SNAPSHOT
          <范围>测试
        
    
  2. @RunWith 注释添加到您的测试中:

    @RunWith(FacesMockitoRunner.class)
    公共类我的测试{
    
  3. 使用注释注入常见的 Faces 对象:

    <前><代码>@Inject
    FacesContext facesContext;
    @注入
    外部上下文扩展;
    @注入
    HttpServletRequest请求;

  4. 使用注释模拟任何其他对象 @org.mockito.Mock (看起来 FacesMockitoRunner 调用这是在幕后进行的,因此这里可能没有必要):

    @Mock MyUserService userService;
    @Mock MyFacesBroker 经纪人;
    @Mock MyUser 用户;
    
  5. 使用

    初始化注入的模拟

    @Before public void initMocks() {
        // 从上面初始化模拟
        MockitoAnnotations.initMocks(this);
    }
    
  6. 使用

    照常设置测试来初始化 :

    assertSame(FacesContext.getCurrentInstance(), facesContext);
    当(ext.getSessionMap()).thenReturn(会话);
    assertSame(FacesContext.getCurrentInstance().getExternalContext(), ext);
    assertSame(FacesContext.getCurrentInstance().getExternalContext().getSessionMap(), ext.getSessionMap());
    

等等。

This article from Oracle shows an approach to "injecting" an EJB for testing using JUnit and Mockito:
http://www.oracle.com/technetwork/articles/java/unittesting-455385.html

Edit:
Basically the inclusion of Mockito allows for mocking objects like EntityManager etc.:

import static org.mockito.Mockito.*;

...

em = mock(EntityManager.class);

They show the approach for EJB as well using mockito. Given an EJB:

@Stateless
public class MyResource {
 @Inject
 Instance<Consultant> company;

 @Inject
 Event<Result> eventListener;

The test can "inject" those objects:

public class MyResourceTest {

 private MyResource myr;
 @Before
 public void initializeDependencies(){
 this.myr = new MyResource();
 this.myr.company = mock(Instance.class);
 this.myr.eventListener = mock(Event.class);
 }

Note that MyResource and MyResource are in the same class path but different source folders so your tests have access to the protected fields, company and eventListener.


Edit:

Note: you can use FacesMockitoRunner from JBoss (https://community.jboss.org/thread/170800) to get this done for the common JSF components and use annotations for the others (Java EE 6 with CDI enabled as a pre-requisite for this, but does not require JBoss server):

  1. Include jsf, mockito, and jsf-mockito dependencies in maven:

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>  
            <version>1.9.5</version> 
            <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.jboss.test-jsf</groupId>
          <artifactId>jsf-mockito</artifactId>
          <version>1.1.7-SNAPSHOT</version>
          <scope>test</scope>
        </dependency>
    
  2. Add the @RunWith annotation to your test:

    @RunWith(FacesMockitoRunner.class)
    public class MyTest {
    
  3. Inject common Faces objects using annotations:

    @Inject
    FacesContext facesContext;
    @Inject
    ExternalContext ext;
    @Inject
    HttpServletRequest request;
    
  4. Mock any other objects using the annotations @org.mockito.Mock (it appears FacesMockitoRunner calls this behind the scenes so it may not be necessary here):

    @Mock MyUserService userService;
    @Mock MyFacesBroker broker;
    @Mock MyUser user;
    
  5. Init the Injected Mocks using the

    @Before public void initMocks() {
        // Init the mocks from above
        MockitoAnnotations.initMocks(this);
    }
    
  6. Setup your test as usual:

    assertSame(FacesContext.getCurrentInstance(), facesContext);
    when(ext.getSessionMap()).thenReturn(session);
    assertSame(FacesContext.getCurrentInstance().getExternalContext(), ext);
    assertSame(FacesContext.getCurrentInstance().getExternalContext().getSessionMap(), ext.getSessionMap());
    

etc.

当梦初醒 2024-10-26 18:33:59

您可能想看看 testfun-JEE 它允许您进行单元测试(而不是集成测试) )容器外部的 EJB。
testfun-JEE 负责将 EJB 以及 EntityManager 和一些标准资源直接注入到您的测试类中 - 这些 EJB 中对其他 EJB 的引用会自动解析。

最酷的是,您可以通过简单地将成员变量添加到用 @Mock 注释的测试中来模拟任何依赖项 - testfun-JEE 将在需要的地方注入此模拟。

请参阅 https://github.com/michaelyaakoby/testfun 中的示例。

顺便说一句,虽然这个框架是最近发布的(就像今天......),但它在我的公司广泛使用了一年多。

You may want to take a look at testfun-JEE which allows you to unit-test (not integration-test) your EJBs outside of a container.
testfun-JEE takes care for injecting EJBs as well as EntityManager and some standard resource directly into your test class - references within these EJBs to other EJBs are resolved automatically.

And the coolest thing is that you can mock any dependency by simple adding a member variable to your test annotated with @Mock - testfun-JEE will inject this mock wherever needed.

See examples in https://github.com/michaelyaakoby/testfun.

BTW, while this framework was published very recently (like today...) it is being widely used for over a year in my company.

黄昏下泛黄的笔记 2024-10-26 18:33:59

使用框架,例如 Mockito。

不幸的是,Arquillian 不会自动包含必要的依赖项。
您可以将它们添加到您的 @Deployment 函数中:

@Deployment  
public static WebArchive deploy()  
{  
  return ShrinkWrap.create(WebArchive.class)  
        .addAsLibraries( // add maven resolve artifacts to the deployment  
            DependencyResolvers.use(MavenDependencyResolver.class)  
            .artifact("org.mockito:mockito-all:1.8.3")  
            .resolveAs(GenericArchive.class))  
        );  
}  

source

然后在您的您可以使用的 @Test 方法:

mock(MockedService.class).methodName()

这个 github 展示展示了一种允许自动发现的方法,这似乎需要一些设置:
来源

Work with a framework, like Mockito.

Unfortunately, Arquillian does not automatically include the necessary dependencies.
You can add them in your @Deployment function:

@Deployment  
public static WebArchive deploy()  
{  
  return ShrinkWrap.create(WebArchive.class)  
        .addAsLibraries( // add maven resolve artifacts to the deployment  
            DependencyResolvers.use(MavenDependencyResolver.class)  
            .artifact("org.mockito:mockito-all:1.8.3")  
            .resolveAs(GenericArchive.class))  
        );  
}  

source

Then in your @Test method you could use:

mock(MockedService.class).methodName()

This github showcase shows a way to allow auto discovery, which seems to require some setup:
source

北凤男飞 2024-10-26 18:33:59

如果您确实想在集成测试中与模拟进行交互(例如,一个原因可能是您还没有完整的实现,或者您有一个无法控制的外部系统的外观),那么有将 Mockito 与 Arquillian 测试集成的一种非常简单的方法,请查看 这个展示中的示例。它实际上是一个独立的扩展,但没有作为一个版本发布。

If you really want to interact with mocks in your integration tests (for instance one reason might be that you don't have a full blown implementation yet or you have an facade to external systems which you don't have control over), there is quite an easy way to integrate Mockito with your Arquillian tests, have a look at this example from the showcase. It's actually extension on its own, but not released as one.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文