Мок-экземпляр‹Класс‹?›› с аннотацией Inject&Any в Junit

В моем проекте javaee есть интерфейс:

public interface SomeInterface{...}

и несколько реализаций:

@Stateless(name = "ImplementationA")
public class ImplementationA implements SomeInterface{...}

@Stateless(name = "ImplementationB")
public class ImplementationB implements SomeInterface{...}

Чтобы получить доступ ко всем реализациям, у меня есть следующее в другом классе:

@Singelton
public class AnotherClass{
    @Inject
    @Any
    private Instance<SomeInterface> impls;

    public SomeInterface someMethod(){
        for(SomeInterface imp : impls){
            if(imp.oneMethod()){
                return imp;
            }
        }
        return null;
    }
}

Если я хочу выполнить модульный тест для этого «AnotherClass», как мне издеваться над

Instance<SomeInterface> impls

поле?

Пробовал @Mock, @Spy, не смог правильно высмеять «имплы» из Mockito, когда тест запускается, «имплы» всегда равны нулю.

Сам модульный тест выглядит следующим образом:

@RunWith(MockitoJUnitRunner.class)
public class SomeTestClass {
    @InjectMocks
    AnotherClass anotherClass;

    @Spy // Here I tried @Mock as well
    private Instance<SomeInterface> impls;

    @Test
    public void TestSomeMethod(){
        Assert.assertTrue( anotherClass.someMethod() == null ); // <- NullPointerException here, which indicates the impls is null instead of anotherClass.
    }
}

Пришлось добавить еще один метод в этот «AnotherClass», чтобы принять экземпляр Instance impls, созданный в модульном тесте, который работает, но уродлив, потому что другой нерелевантный метод должен быть добавлен только для целей модульного теста.

Любая идея, как выглядит правильный способ выполнения модульного теста?

Версия Mockito и Junit:

group: 'junit', name: 'junit', version: '4.12'
group: 'org.mockito', name: 'mockito-core', version:'2.12.0'

Заранее спасибо.


person jonesir    schedule 28.05.2018    source источник
comment
не могли бы вы также добавить свой тест JUnit? было бы неплохо взглянуть, иначе есть вероятность, что вы делаете что-то не так.   -  person ikos23    schedule 28.05.2018
comment
@john добавил модульный тест, спасибо за вопрос.   -  person jonesir    schedule 29.05.2018
comment
не могли бы вы также добавить информацию о том, какую версию mockito вы используете?   -  person ikos23    schedule 29.05.2018
comment
а также требуется полная подпись для xxxx(). он недействителен?   -  person ikos23    schedule 29.05.2018
comment
@john обновил метод, он не является недействительным, имеет ли это значение? Я также добавил утверждение, которое проверяет, существует ли желаемая реализация, если нет, оно должно равняться нулю, но это не само утверждение, которое не удалось. Спасибо еще раз.   -  person jonesir    schedule 29.05.2018


Ответы (1)


Что вы могли бы попробовать сделать:

  1. Добавьте некоторые ожидания, если они вам нужны. Вероятно, вам понадобится этот impls.xxxx() для вызова реального метода, если это шпион (думаю, это поведение по умолчанию).

  2. Возможно, также попробуйте сначала инициализировать mocks:

    @RunWith(MockitoJUnitRunner.class)
    public class SomeTestClass {
        @InjectMocks
        AnotherClass anotherClass;
    
        @Spy  
        private Instance<SomeInterface> impls;
    
        // init here
        @Before 
        public void initMocks() {
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void TestSomeMethod(){
            anotherClass.someMethod(); // <- NullPointerException here, which indicates the impls is null instead of anotherClass.
        }
    }
    

Этот вызов инициализации должен быть где-то в базовом классе или в программе запуска теста.

Странно, без него не работает, думаю, если вы используете MockitoJUnitRunner, он должен работать.

ОБНОВЛЕНИЕ:


Прошло много времени, но я вижу, что есть несколько новых комментариев, поэтому добавлю дополнительную информацию.

Это тест, который работает.

// ImplementationA.oneMethod simply returns TRUE in my case
// ImplementationB.oneMethod simply returns FALSE
@RunWith(MockitoJUnitRunner.class)
public class AnotherClassTest {

    @Spy // can be Mock
    Instance<SomeInterface> impls;

    @InjectMocks
    AnotherClass classUnderTest;

    @Mock
    Iterator<SomeInterface> iterator; // why need it - check below :)

    @Test
    public void someMethod() {
        when(impls.iterator()).thenReturn(iterator);
        when(iterator.hasNext()).thenReturn(true).thenReturn(false);
        when(iterator.next()).thenReturn(new ImplementationA());
        
        SomeInterface res = classUnderTest.someMethod();
        System.out.println("done");
    }
}

Где была проблема? Здесь:

public SomeInterface someMethod() {
    
    // explanation: For-Each uses iterator
    // if we do not mock Instance<SomeInterface> impls properly
    // impls.iterator() under the hood will return NULL -> NPE
    for (SomeInterface imp : impls) {
        if (imp.oneMethod()) {
            return imp;
        }
    }
    return null;
}

Вот почему в моем тесте я также создаю фиктивный итератор (Mock). Мне также нужно предоставить некоторые ожидания, чтобы заставить его работать, и вот они:

when(impls.iterator()).thenReturn(iterator); // returns my mock
when(iterator.hasNext()).thenReturn(true).thenReturn(false);
when(iterator.next()).thenReturn(new ImplementationA());

Надеюсь, это понятно :) Благодаря этому for-each работает нормально и возвращает ImplementationA.

Удачного взлома :)

person ikos23    schedule 29.05.2018
comment
Спасибо за этот пример, этот дополнительный блок не помог. Я не совсем понимаю ваш пункт 1. На самом деле в someMethod() использовались impls, это то, что вы имели в виду? Или мне нужно сделать вызов метода для impls в модульном тесте? - person jonesir; 29.05.2018
comment
@jonesir извините за это, я обновлю пример через минуту - person ikos23; 29.05.2018
comment
@ikos23 как ты это решил, пожалуйста? - person greengold; 02.12.2020
comment
@greengold, пожалуйста, проверьте мое обновление в разделе UPD в моем ответе. - person ikos23; 03.12.2020