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