Как в рамках юнит-теста junit заставить работать методы бина
Есть сервис, который надо протестировать. (unit-тесты. не интеграционные)
@Service
public class PersonService {
@Autowired
private PersonRepo repo;
@Autowired
private CreateHelper helper;
@Autowired
private ModelMapper mapper;
public Person create(CreatePersonParams params) {
helper.check(params);
Person person = mapper.map(params, Person.class);
repo.saveAndFlush(person);
}
}
@ExtendWith(SpringExtension.class)
class PersonServiceTest {
@Mock
private PersonRepo repo;
@Mock
private CreateHelper helper;
@InjectMock
private PersonService service;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
void create() {
// test-logic
}
}
Вот у меня есть метод create. CreateHelper уже протестирован, соответственно я ставлю на него заглушку. От БД надо изолироваться, соответственно на PersonRepo я тоже ставлю заглушку. А вот mapper, по факту, должен реализовать то, зачем он тут находится (иначе зачем тестировать подмененную логику?).
Если настроить тест, предопередлисв поведение моков, то вывалится NPE, в том месте, где используется mapper.
Как не поднимая контекста заставить тестер исполнять методы ModelMapper?
Ответы (1 шт):
Если хочется тестировать именно с реальным ModelMapper, то нужно именно его и проставлять в PersonService.
Сделать это можно либо с помощью ReflectionTestUtils из spring-test:
void setUp() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(service, "mapper", createRealMapper());
}
У этого способа минус в том, что используется reflection со всеми ее минусами (главный, что нет проверки типов во время компиляции).
Лучше изменить подход к конфигурированию зависимостей, а именно использовать для этого зависимости внедряемые через конструктор:
@Service
public class PersonService {
private final PersonRepo repo;
private final CreateHelper helper;
private final ModelMapper mapper;
public PersonService(PersonRepo repo, CreateHelper helper, ModelMapper mapper) {
this.repo = repo;
this.helper = helper;
this.mapper = mapper;
}
public Person create(CreatePersonParams params) {
...
}
}
@ExtendWith(MockitoExtension.class)
class PersonServiceTest {
@Mock
private PersonRepo repo;
@Mock
private CreateHelper helper;
private PersonService service;
@BeforeEach
void setUp() {
service = new Service(repo, helper, createRealMapper());
}
@Test
void create() {
// test-logic
}
}
Чтобы не плодить конструкторы рекомендую использовать lombok тогда все еще более упростится:
@Service
@RequiredArgsConstructor
public class PersonService {
private final PersonRepo repo;
private final CreateHelper helper;
private final ModelMapper mapper;
public Person create(CreatePersonParams params) {
...
}
}
Ну и последнее. В данном случае имеет смысл протестровать логику ModelMapper в отдельном тесте. А логику PersonService тестировать либо полностью с использованием моков либо (если он содержит так мало кода и довольно простой) с помощью интеграционного теста, который в любом случае нужен. Такой тест будет создавать spring контекст.