Model Mapper работает с Live Code, но не работает во время JUNIT.

Фон

У меня есть простое приложение SpringBoot, в котором я тестирую ОБНОВЛЕНИЕ моего объекта домена из DTO. Естественно - я использую ModelMapper для преобразования из DTO- ›Entity. Проблема, с которой я сталкиваюсь, заключается в том, что, хотя ModelMapper отлично работает в режиме реального времени, он не работает во время JUNIT. Я помещаю точку останова в initBaseModelMapper в моем файле конфигурации во время выполнения как JUNIT, так и LIVE, и точка останова успешно срабатывает. Но в JUNITS во время фактического сопоставления - значения null все еще применяются к объекту домена, но не во время динамического запуска, который работает идеально.

Конфигурация

@Configuration
public class ModelMapperConfiguration {
    @Bean(name = "myEntityMapper")
    public ModelMapper modelMapper() {
        return initBaseModelMapper();
    }
    
    public static ModelMapper initBaseModelMapper() {
        ModelMapper modelMapper = new ModelMapper();
        modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
        modelMapper.getConfiguration().setSkipNullEnabled(true); // Tried without this as well
        return modelMapper; // Gets hit during LIVE and JUNITS
    }
}

Тестируемый метод основного класса

public class MyCaseService {
    @Autowired
    @Qualifier("myEntityMapper")
    private ModelMapper modelMapper;

    @Override
    @Transactional
    public @ResponseBody
    MyCaseEntity updateMyCase(
            @Valid final String myCaseId,
            @Valid MyCaseDTO myCase) throws Exception {

        MyCaseEntity existingEntity = entityRepository.find(myCaseId);
        modelMapper.map(myCase, existingEntity);
        return existingEntity;
    }

ИЮНЬ

Я поставил точку останова на ModelConfiguration и вижу, как он инициализируется точно так же, как когда код работает в реальном времени. Однако по какой-то причине ModelMapper ИГНОРИРУЕТ пропуск нулевых полей, в отличие от того, когда он работает в реальном времени.

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes= {ModelMapperConfiguration.class})
public class MyCaseServiceTest {
    @InjectMocks
    private MyCaseService testSubject;

    @Spy
    @Qualifier("myEntityMapper")
    private ModelMapper modelMapper;
   
    @Before
    public void setUp() {
        // Initialized `testEntityCase` etc with Id etc
    }

    @Test
    public void testUpdate() throws Exception {
        Mockito.when(entityRepository.find(Mockito.any())).thenReturn(testEntityCase);
        
        MyCaseEntity myCase = testSubject.updateMyCase(
                "1", 
                testCaseDTO);
        
        assertEquals(1L, myCase.getId().longValue()); // <- Test Fails with NullPointer. Id becomes null during  JUNIT.
    }

person Dorian McAllister    schedule 25.11.2020    source источник
comment
Я думаю, у вас есть 2 ModelMappers, созданный при запуске приложения и Spy, который будет использоваться в тесте. Попробуйте установить skipNull для шпиона в программе установки или попробуйте без шпиона   -  person Turo    schedule 25.11.2020
comment
@turo Вы правы. Шпион вводит свой собственный. Но я бы предпочел, чтобы JUNIT имитировал запуск приложения ModelConfiguration. Причина в том, что завтра, если кто-то испортит конфигурацию модели - я хочу, чтобы мой модульный тест это поймал. Если я переопределю его в своем JUNIT - я думаю, что тест пройдет, но он упустит момент обнаружения ошибки кода. Почему при начальной загрузке JUNIT не используется предварительно инициализированный ModelMapper в ContextConfiguration, который я предоставляю в тестовом классе? Также без Spy не будет работать, поскольку ModelMapper не будет вводиться в MyCaseService testSubject   -  person Dorian McAllister    schedule 25.11.2020
comment
@Spy создает новый неинициализированный ModelMapper, который впоследствии будет автоматически подключен, для этого и нужны шпионы. Похоже, что ваша конфигурация не создает Службу, поэтому нет автоматического подключения, просто насмешки / шпионы вводятся через @InjectMocks   -  person Turo    schedule 25.11.2020
comment
@Turo Спасибо за это. Это действительно заставило меня задуматься, и я потратил прошедший день, пытаясь найти альтернативу. Я не могу выполнить полную загрузку своего приложения, поэтому я просто ввожу конфигурации в свой OP. Но мне ДЕЙСТВИТЕЛЬНО нужен тот же шпион для перехвата вызовов, иначе я вынужден ДУБЛИРОВАТЬ ModelMapperConfiguration в моем JUNIT, что просто плохо. Если завтра, если кто-то изменит Live ModelMapperConfiguration - JUNIT не будут отражать их, поскольку я дублирую конфигурацию mapper в своем тесте. Ищете альтернативы (т.е. вы на 100% правы - тестовая служба не подключена автоматически, как в OP)   -  person Dorian McAllister    schedule 28.11.2020


Ответы (1)


Один из способов преодолеть эти проблемы - автоматически подключить конструкцию MyCaseService к частному члену.

public class MyCaseService {

    private ModelMapper modelMapper;

    @Autowired 
    MyCaserService(@Qualifier("myEntityMapper") ModelMapper modelMapper) {
        this.modelMapper = modelMapper;
    }

    @Override
    @Transactional
    public @ResponseBody
    MyCaseEntity updateMyCase(
            @Valid final String myCaseId,
            @Valid MyCaseDTO myCase) throws Exception {

        MyCaseEntity existingEntity = entityRepository.find(myCaseId);
        modelMapper.map(myCase, existingEntity);
        return existingEntity;
    }
}

В Тесте вы можете использовать Шпион для создания Сервиса.

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes= {ModelMapperConfiguration.class})
public class MyCaseServiceTest {

    @Spy
    @Qualifier("myEntityMapper")
    private ModelMapper modelMapper;

    private MyCaseService testSubject;
   
    @Before
    public void setUp() {
        testSubject = new MyCaseService(modelMapper);
        // Initialized `testEntityCase` etc with Id etc
    }
...
person Turo    schedule 28.11.2020
comment
Спасибо. Это кажется простым и логичным - не знаю, почему я не рассмотрел Constructor DI :) Оцените это !! - person Dorian McAllister; 28.11.2020
comment
Рад, что смог помочь! - person Turo; 28.11.2020