Как в рамках юнит-теста 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 шт):

Автор решения: Roman Konoval

Если хочется тестировать именно с реальным 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 контекст.

→ Ссылка