Как внедрить только один макет внутри класса обслуживания с обычным созданием экземпляра в другие экземпляры?

#spring-boot #mockito #powermockito #spring-boot-test

#spring-boot #mockito #powermockito #spring-boot-test

Вопрос:

Предположим, я реализовал следующий класс обслуживания:

 @Service
@RequiredArgsConstructor
@Transactional
public class ItemService {

    private final ItemRepository itemRepository;
    private final ItemCreatorFactory itemCreatorFactory;
    private final CommonClient commonClient;
    private final HelperService helperService;
    private final PrivilegeUtil privilegeUtil;

    public void addItem() {
        //How to only mock this in tests and other defaults to normal Autowired injection
        if(!privilegeUtil.isAdmin("bla bla")) {
             Throw new InvalidOperationException("Only super admin can insert data.");
        }
        helperService.doSomeRealLogic();
        commonClient.callAnotherExternalAPI();
        itemRepository.save(myObject);
    }
    // other methods
}
  

И мне нужно протестировать вставку нового элемента в базу данных, но перед этим я выполняю дополнительную логику, и если все в порядке, я могу безопасно вставить в базу данных.

Теперь мне нужно использовать mockito, поэтому я издеваюсь только над классом privilegeUtil, но мне нужно, чтобы все остальные экземпляры внутри класса ItemService были автоматически подключены, не издеваясь над ним.

Итак, я перехожу к следующему тестовому классу:

 @RunWith(SpringRunner.class)
public class ItemTests {

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @InjectMocks//Only mocks privilegeUtil but other instances is all null :(
    private ItemService itemService;//So how i get autowired item service instance but only mock privilegeUtil instance inside it and all other instances go for normal instantiation ???

    @Mock
    private PrivilegeUtil privilegeUtil;

    @Test
    public void testAddNewItem() {
        String email = "super@mail.com";

        Mockito.when(privilegeUtil.isSuperAdmin(email)).thenReturn(true);
        
        //Here i need to test adding item normally
        //But only skip checking isSuperAdmin part inside addItem method
        itemService.addItem(itemRequest, email);
    }

}
  

Итак, как я получаю экземпляр службы autowired item, но внутри него есть только экземпляр mock privilegeUtil, а все остальные экземпляры идут для обычного создания экземпляра???

Возможно ли это? а если нет, то что вы можете предложить?

Ответ №1:

Да, это возможно. Поскольку вы упоминаете, что тестируете некоторую логику, связанную с базой данных, я думаю, имеет смысл использовать @DataJpaTest , чтобы позволить Spring создать для вас нарезанный контекст со всеми компонентами, связанными с JPA / database.

Затем вы уже можете автоматически подключить свой репозиторий к своему тесту.

Что осталось, так это вручную создать все остальные обычные компоненты, используя, например @TestConfiguration . Те классы, которые вы хотите имитировать, аннотируйте их, @MockBean чтобы Spring помещал отредактированную версию в нарезанный контекст.

 @RunWith(SpringRunner.class)
@DataJpaTest
public class ItemTests {

  @TestConfiguration
  static class TestConfig {

    @Bean
    public ItemCreatorFactory itemCreatorFactory() {
      return new ItemCreatorFactory();
    }

   // .. manually create all other beans

  }

  @Autowired
  private ItemRepository itemRepository;

  @Autowired
  private ItemCreatorFactory itemCreatorFactory;

  @Autowired
  private CommonClient commonClient;

  @Autowired
  private HelperService helperService;

  @MockBean
  private PrivilegeUtil privilegeUtil;

  @Before
  public void setUp() throws Exception {
     this.itemService = new ItemService(itemRepository, itemCreatorFactory, commonClient, helperService, mockedPrivilegeUtil);
  }

  @Test
  public void testAddNewItem() {
      String email = "super@mail.com";
      
      Mockito.when(privilegeUtil.isSuperAdmin(email)).thenReturn(true);
    
      itemService.addItem(itemRequest, email);
  }

}
  

… и тогда вы, наконец, можете вручную создать свой ItemService экземпляр и передать все свои экземпляры конструктору.