Как использовать внедрение зависимостей с модульным тестированием Spring Boot?

Можно ли использовать внедрение зависимостей с модульными тестами с помощью Spring Boot? Для интеграционного тестирования @SpringBootTest запустите весь контекст приложения и службы контейнера. Но можно ли включить функциональность внедрения зависимостей на уровне детализации модульного теста?

Вот пример кода

@ExtendWith(SpringExtension.class)
public class MyServiceTest {
    
    @MockBean
    private MyRepository repo;
    
    @Autowired
    private MyService service; // <-- this is null
    
    @Test
    void getData() {
        MyEntity e1 = new MyEntity("hello");
        MyEntity e2 = new MyEntity("world");
        
        Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
        
        List<String> data = service.getData();
        
        assertEquals(2, data.size());
    }
}

@Service
public class MyService {
    
    private final MyRepository repo; // <-- this is null
    
    public MyService(MyRepository repo) {
        this.repo = repo;
    }
    
    public List<String> getData() {
        return repo.findAll().stream()
                .map(MyEntity::getData)
                .collect(Collectors.toList());
    }
}

Или я должен просто управлять SUT (классом обслуживания) как POJO и вручную вводить издевательские зависимости? Я хочу, чтобы тесты выполнялись быстро, но при этом сводился к минимуму шаблонный код.

Если вам нужен модульный тест, не используйте внедрение зависимостей, если вы хотите, чтобы интеграционный тест или системный тест использовали внедрение. Здесь это ничего не добавляет, кроме сложности и медленных тестов. Просто замените @Autowired на @InjectMocks и @MockBean на @Mock и используйте подходящую бегущую мокито вместо удлинителя пружины. Или просто вручную создайте макет и экземпляр вашего сервиса (еще быстрее).

M. Deinum 11.12.2020 08:43
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
1
2 972
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы не добавили @Autowired в сервис для MyRepository

Класс обслуживания

@Service
public class MyService {
    
    private final MyRepository repo; // <-- this is null
    
    @Autowired
    public MyService(MyRepository repo) {
        this.repo = repo;
    }
    
    public List<String> getData() {
        return repo.findAll().stream()
                .map(MyEntity::getData)
                .collect(Collectors.toList());
    }
}

Класс испытаний службы

@ExtendWith(MockitoExtension.class)
public class MyServiceTest {
    
    @Mock
    private MyRepository repo;
    
    @InjectMocks
    private MyService service;
    
    @Test
    void getData() {
        MyEntity e1 = new MyEntity("hello");
        MyEntity e2 = new MyEntity("world");
        
        Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));
        
        List<String> data = service.getData();
        
        assertEquals(2, data.size());
    }
}
Ответ принят как подходящий

Как упоминал @M.Deinum в комментариях, модульные тесты не должны использовать внедрение зависимостей. Мокайте MyRepository и внедряйте MyService с помощью Mockito (и Junit5):

@ExtendWith(MockitoExtension.class)
public class MyServiceTest {

    @InjectMocks
    private MyService service;

    @Mock
    private MyRepository repo;

    @Test
    void getData() {
        MyEntity e1 = new MyEntity("hello");
        MyEntity e2 = new MyEntity("world");

        Mockito.when(repo.findAll()).thenReturn(Arrays.asList(e1, e2));

        List<String> data = service.getData();

        assertEquals(2, data.size());
    }
}

Если вы хотите протестировать репозиторий, используйте @DataJpaTest. Из документов:

Использование этой аннотации отключит полную автоматическую настройку и вместо этого применять только конфигурацию, относящуюся к тестам JPA.

@DataJpaTest
public class MyRepositorTest {

    @Autowired
    // This is injected by @DataJpaTest as in-memory database
    private MyRepository repo;

    @Test
    void testCount() {
        repo.save(new MyEntity("hello"));
        repo.save(new MyEntity("world"));

        assertEquals(2, repo.count());
    }
}

В заключение, предлагаемый подход состоит в том, чтобы протестировать уровень службы, имитирующий уровень репозитория, с помощью Mockito (или аналогичной библиотеки) и протестировать уровень репозитория с помощью @DataJpaTest.

Другие вопросы по теме