Модульное тестирование EJB с Mockito, TestNG и OpenEJB

У меня есть следующие 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);
    }
}

Теперь я хочу заменить реализацию RemotePersonService в PersonServiceImpl.java макетом с использованием 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);
    }
}

Но это не работает. @Mock RemotePersonService не внедряется в PersonService, и по-прежнему используется настоящий EJB.

Как я могу заставить это работать?


person Yotus    schedule 14.07.2015    source источник
comment
Не используйте аннотации для своих тестов. Имейте конструктор, который будет подключать все ваши зависимости. Создавайте макеты и передайте их ему.   -  person duffymo    schedule 14.07.2015
comment
Изменение типа PersonService на PersonServiceImpl в PersonServiceTest, по-видимому, помогает. Но разве нельзя использовать интерфейс вместо реализации?   -  person Yotus    schedule 14.07.2015
comment
Вы должны быть явными здесь. Если вы используете PersonService, как узнать, какая реализация внедряется и тестируется? Использование PersonServiceImpl говорит вам об этом.   -  person Kai    schedule 14.07.2015
comment
Но в том-то и дело, что вам не нужно знать тип, который вводится.   -  person duffymo    schedule 14.07.2015
comment
Не разрешается использовать @InjectMocks в интерфейсах, потому что вы должны тестировать конкретные классы, а не интерфейсы. Итак, как вы прокомментировали ранее, вы должны заменить интерфейс, в который вводятся макеты, конкретным классом.   -  person Mindaugas    schedule 14.07.2015


Ответы (2)


Не используйте аннотации для своих тестов. Имейте конструктор, который будет подключать все ваши зависимости.

@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);
    }
}
person duffymo    schedule 14.07.2015
comment
Это не сработает, так как PersonService — это интерфейс, и вы не можете их создать. Кроме того, написание конструкторов или сеттеров, которые будут использоваться только в модульных тестах для внедрения моков, — это просто немного накладно, когда их можно просто внедрить с помощью аннотаций. - person Mindaugas; 14.07.2015
comment
Опечатка; должен был быть PersonServiceImpl. Вы можете создать экземпляр этого. Не накладные расходы - банально. Аннотации также должны создавать экземпляры объектов. Можно утверждать, что обработка аннотаций является более накладной, чем просто создание экземпляров ваших зависимостей. Все дело в том, что он не может заставить его работать с аннотациями. Это сработает. - person duffymo; 14.07.2015
comment
Что ж, используя PersonServiceImpl, он может заставить его работать с аннотациями (потому что, как он сказал, у него уже это получилось). - person Mindaugas; 14.07.2015
comment
Изменение типа PersonService на PersonServiceImpl в PersonServiceTest, по-видимому, помогает. - видимо да. - person Mindaugas; 14.07.2015
comment
Я могу заставить его работать, используя конкретный класс PersonServiceImpl в тесте, но я хотел знать, можно ли вместо этого использовать интерфейс. Но, по-видимому, это невозможно сделать из-за того, как Mockito внедряет моки. - person Yotus; 15.07.2015

Я использую сеттеры для класса и Lookup для ejb.

      private ServicioAsyncMessaging servicioNotificaciones;

Я удаляю @EJB --› и на геттере

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

С этими изменениями мокито --› внедряет моки в сеттер.

person cabaji99    schedule 19.05.2021