【问题标题】:Unit testing an EJB with Mockito, TestNG and OpenEJB使用 Mockito、TestNG 和 OpenEJB 对 EJB 进行单元测试
【发布时间】:2015-07-14 10:23:24
【问题描述】:

我有以下 EJB:

PersonService.java

@Local
public interface PersonService {
    long countPersons();
}

PersonServiceImpl.java

@Stateless
public class PersonServiceImpl implements PersonService {

    @EJB
    private RemotePersonService remotePersonService;

    @Override
    public long countPersons() {
         return remotePersonService.getAllPersons().size();
    }
}

RemotePersonService.java

@Local
public interface RemotePersonService {
    List<Person> getAllPersons();
}

RemotePersonServiceImpl.Java

@Stateless
public class RemotePersonServiceImpl {
    @Override
    public List<Person> getAllPersons() {
        // Here, I normally call a remote webservice, but this is for the purpose of this question
        List<Person> results = new ArrayList<Person>();
        results.add(new Person("John"));
        return results;
    }
}

这是我的测试

AbstractTest.java

public abstract class AbstractTest {

    private InitialContext context;

    @BeforeClass(alwaysRun = true)
    public void setUp() throws Exception {
        System.setProperty("java.naming.factory.initial", "org.apache.openejb.client.LocalInitialContextFactory");

        Properties properties = new Properties();
        properties.load(getClass().getResourceAsStream("/unittest-jndi.properties"));

        context = new InitialContext(properties);
        context.bind("inject", this);

    }

    @AfterClass(alwaysRun = true)
    public void tearDown() throws Exception {
        if (context != null) {
            context.close();
        }
    }
}

PersonServiceTest.java

@LocalClient
public class PersonServiceTest extends AbstractTest {

    @EJB
    private PersonService personService;

    @Test
    public void testPersonService() {
        long count = personService.countPersons();

        Assert.assertEquals(count, 1l);
    }
}

现在,我想做的是将 PersonServiceImpl.java 中的 RemotePersonService 实现替换为使用 Mockito 的模拟,并且在我的 testPersonService 方法中仍然有相同的调用。

我试过了:

PersonServiceTest.java

@LocalClient
public class PersonServiceTest extends AbstractTest {

    @Mock
    private RemotePersonService remotePersonService;

    @EJB
    @InjectMocks
    private PersonService personService;

    @BeforeMethod(alwaysRun = true)
    public void setUpMocks() {
        MockitoAnnotations.initMocks(this);

        List<Person> customResults = new ArrayList<Person>();
        customResults.add(new Person("Alice"));
        customResults.add(new Person("Bob"));

        Mockito.when(remotePersonService.getAllPersons()).thenReturn(customResults);
    }

    @Test
    public void testPersonService() {
        long count = personService.countPersons();

        Assert.assertEquals(count, 2l);
    }
}

但这不起作用。在PersonService中没有注入@Mock RemotePersonService,仍然使用真正的EJB。

我怎样才能完成这项工作?

【问题讨论】:

  • 不要在测试中使用注释。有一个构造函数来连接你的所有依赖项。创建模拟并将它们传递给它。
  • PersonServiceTest 中将类型 PersonService 更改为 PersonServiceImpl 显然可以解决问题。但是不应该使用接口而不是实现吗?
  • 你应该在这里明确。如果您使用PersonService,您如何知道注入并测试了哪个实现?使用PersonServiceImpl 会告诉你这一点。
  • 但这就是重点——您不必知道注入的类型。
  • 不允许@InjectMocks 进入接口,因为你应该测试具体的类而不是接口。因此,正如您之前评论的那样,您应该将模拟注入的接口替换为具体的类

标签: java ejb testng mockito openejb


【解决方案1】:

不要在测试中使用注释。有一个构造函数来连接你的所有依赖项。

@Stateless
public class PersonServiceImpl implements PersonService {

    @EJB
    private RemotePersonService remotePersonService;

    // Let your test instantiate a mock service and wire it into your test instance using this constructor.
    public PersonServiceImpl(RemotePersonService rps) {
        this.remotePersonService = rps;
    }

    @Override
    public long countPersons() {
         return remotePersonService.getAllPersons().size();
    }
}

创建模拟并将它们传递给它。您的测试可能如下所示:

@LocalClient
public class PersonServiceTest extends AbstractTest {

    @Test
    public void testPersonService() {
        RemotePersonService mockRemotePersonService = Mockito.mock(RemotePersonService.class);
        List<Person> customResults = new ArrayList<Person>();
        customResults.add(new Person("Alice"));
        customResults.add(new Person("Bob"));
              Mockito.when(mockRemotePersonService.getAllPersons()).thenReturn(customResults);
        PersonService personService = new PersonServiceImpl(mockRemotePersonService);
        long count = personService.countPersons();    
        Assert.assertEquals(count, 2l);
    }
}

【讨论】:

  • 这行不通,因为 PersonService 是一个接口,你不能实例化它们。同样,编写仅在单元测试中用于注入模拟的构造函数或设置器只是一点开销,因为可以使用注释注入它们。
  • 错字;应该是 PersonServiceImpl。你可以实例化它。不是开销 - 微不足道。注释也必须实例化对象。有争议的是,处理注释比简单地实例化您的依赖项开销更大。关键是他不能让它与注释一起工作。这会奏效。
  • 使用 PersonServiceImpl 他可以让它与注释一起工作(因为他说他已经做到了)。
  • “在 PersonServiceTest 中将 PersonService 类型更改为 PersonServiceImpl 显然可以解决问题。” - 显然是的。
  • 我可以在测试中使用具体类 PersonServiceImpl 使其工作,但我想知道是否可以使用该接口。但显然,由于 Mockito 注入模拟的方式,这样做本质上是不可能的。
【解决方案2】:

我在类上使用 setter,并查找 ejb

      private ServicioAsyncMessaging servicioNotificaciones;

我删除了@EJB --> 和getter

    public ServicioAsyncMessaging getServicioNotificaciones() {
            if(servicioNotificaciones == null){
             servicioNotificaciones = (ServicioAsyncMessaging)Lookup.getEjb(EjbJndiConstantes.EJB_SERVICIO_ASYNC_MSG);
            }
            return servicioNotificaciones;
        }
    
        public void setServicioNotificaciones(ServicioAsyncMessaging servicioNotificaciones) {
            this.servicioNotificaciones = servicioNotificaciones;
        }

查找es:

public static Object getEjb(String lookupName){
    Object t = null;
    try {
        Context ctx = new InitialContext();
         t=  ctx.lookup(lookupName);
    } catch (NamingException e) {
        log.error("getEjb | Error {}",e.getMessage(),e);
    }
    return t;
}

通过这些更改,mockito --> 在 setter 上注入 mock。

【讨论】:

    猜你喜欢
    • 2017-11-22
    • 1970-01-01
    • 2017-09-19
    • 2019-04-27
    • 1970-01-01
    • 1970-01-01
    • 2020-01-17
    • 2018-01-16
    • 1970-01-01
    相关资源
    最近更新 更多