Как вводить несколько макетов одного и того же интерфейса

Класс Java (называемый ServiceCaller), который я хочу проверить, имеет следующее:

@Autowired @Qualifier(value="serviceA")
SomeService serviceA;

@Autowired @Qualifier(value="serviceB")
SomeService serviceB;

(есть метод doWork(), который проверяет условие и вызывает либо A, либо B).

Как я вставляю макет каждой службы в соответствующую переменную?

У моего Junit есть следующее:

@InjectMocks ServiceCaller classUnderTest = new ServiceCaller();

@Mock SomeService mockServiceA;
@Mock SomeService mockServiceB;

Тем не менее, когда я запускаю свои тесты, чтобы проверить, что служба A/B вызвана в правильном состоянии, я получаю нулевые указатели, поскольку макет не был введен.

Очевидно, что это связано с несколькими зависимостями от одного и того же интерфейса (SomeService). Есть ли способ указать квалификатор при объявлении mock-сервиса? Или мне нужно иметь сеттеры для зависимостей и ставить старомодный способ?

Ответ 1

Этого должно быть достаточно, чтобы назвать ваш mocks serviceA и serviceB. Из документации Mockito:

Ввод инсталляции устройства; mocks сначала будет разрешен по типу, тогда, если есть несколько свойств одного и того же типа, по совпадению имени свойства и имени макета.

В вашем примере:

@InjectMocks ServiceCaller classUnderTest;

@Mock SomeService serviceA;
@Mock SomeService serviceB;

Обратите внимание, что нет необходимости вручную создавать экземпляр класса при использовании @InjectMocks.

Тем не менее, я лично предпочитаю впрыскивать зависимости, используя конструктор. Это облегчает @InjectMocks в тестах (просто вызовите конструктор с вашими издевательствами - без инструментов отражений или @InjectMocks (что полезно, но скрывает некоторые аспекты)). Кроме того, с использованием TDD ясно видно, какие зависимости необходимы для тестируемого класса, а также IDE может генерировать ваши заглушки конструктора.

Spring Framework полностью поддерживает инъекцию конструктора:

@Bean
public class ServiceCaller {
    private final SomeService serviceA;
    private final SomeService serviceB;

    @Autowired
    public ServiceCaller(@Qualifier("serviceA") SomeService serviceA,
                         @Qualifier("serviceB") SomeService serviceB) { ... }

    ...
}

Этот код можно протестировать с помощью:

@Mock SomeService serviceA;
@Mock SomeService serviceB;

//in a setup or test method
ServiceCaller classUnderTest = new ServiceCaller(serviceA, serviceB); 

Ответ 2

вы можете использовать свойство name, чтобы определить свой экземпляр следующим образом:

@Mock(name="serviceA") SomeService serviceA;
@Mock(name="serviceB") SomeService serviceB;

Ответ 3

При наличии однотипных зависимостей mockito прекращает вводить зависимости из-за свойств тех же типов. решить эту проблему со ссылкой на @osiris256 следующим образом.

    Class ServiceLayer{

       @Autowired
       @Qualifier("bean1")
       private InterfaceA typeA;  

       @Autowired
       @Qualifier("bean2")
       private InterfaceA typeb;  

    }

    your test class should be 

    @RunWith(SpringRunner.class)    
    class ServiceLayerTest{

      @Mock(name = "typeA")
      private InterfaceA typeA;  

      @Mock(name = "typeB")
      private InterfaceA typeB;  

      @InjectMocks
       ServiceLayer serviceLayer;

       @Before
       public void initialiseBeforeTest(){
          MockitoAnnotations.initMocks(this);

       }
       // here goes your test 
       @Test
       public void test(){
          // use your when then .....
       }
    }

Примечание: если вы используете SpringRunner и используете @MockBean, это не сработает, вы должны заменить @Mock (name = "") со ссылкой на @osiris256.