UnitTesting difficulty
Closed this issue · 8 comments
Здравейте, господин Балев! Много се затруднявам с тестовете. Постоянно имам грешки от NullPointerException. Парчето код, което искам да тествам, е това :
@service
public class NewsServiceImpl implements NewsService {
private final NewsRepository newsRepository;
private final ModelMapper modelMapper;
public NewsServiceImpl(NewsRepository newsRepository, ModelMapper modelMapper) {
this.newsRepository = newsRepository;
this.modelMapper = modelMapper;
}
@Override
public NewsServiceModel saveNews(NewsServiceModel newsServiceModel) {
News news = modelMapper.map(newsServiceModel,News.class);
return modelMapper.map(newsRepository.save(news), NewsServiceModel.class);
}
А това ми е тестовият код :
@ExtendWith(MockitoExtension.class)
public class NewsServiceImplTest {
private NewsServiceImpl testNSI;
private ModelMapper modelMapper = new ModelMapper();
@Mock
NewsRepository mockNewsRepository;
@BeforeEach
void setup(){
testNSI = new NewsServiceImpl(mockNewsRepository, modelMapper);
}
@Test
public void saveNewsTest(){
NewsServiceModel exampleServiceModel = new NewsServiceModel();
exampleServiceModel.setDescription("Some description in here.");
exampleServiceModel.setFromDate(LocalDate.parse("2023-04-15"));
exampleServiceModel.setTitle("Some Title in here");
exampleServiceModel.setImageUrl("https://m.media-amazon.com/images/W/IMAGERENDERING_521856-T1/images/I/61+yI8cp94L._AC_SL1500_.jpg");
NewsServiceModel newsServiceModel = testNSI.saveNews(exampleServiceModel);
Assertions.assertEquals(newsServiceModel.getDescription(),exampleServiceModel.getDescription());
}
}
И IntelliJ ме пресича с : java.lang.NullPointerException: Cannot invoke "com.example.gametaste.service.impl.NewsServiceImpl.saveNews(com.example.gametaste.model.service.NewsServiceModel)" because "this.testNSI" is null. Не знам защо бъркам, като в сетъпа съм инициализирал testNSI. Ще съм благодарен за помощта Ви !
Здравей!
Може ли линк към проекта за да разгледам фейлналия тест?
Благодаря,
Л.
Здравей!
Проблемът е че си анотирал теста си с анотация @Test
в пакет org.junit.Test
. Ето:
import org.junit.Test;
Това е JUnit4. А BeforeEach
е в JUnit5.
Промени import org.junit.Test;
(JUnit 4) на import org.junit.jupiter.api.Test;
(Junit 5).
Поздрави,
Л.
Благодаря за бързите отговори, господин Балев ! А може ли насоки за теста, който правя в момента ? Добре ли изглежда ? Проверявам ли правилно метода ? Какви насоки може да ми дадете ?
Господин Балев, може ли отново да Ви помоля за помощ ? Последният ми тест deleteById проверявам първо дали пълня репозиторито и после с testNSI изтривам конкретната новина с id 1. Обаче на този ред Assertions.assertEquals(1, mockNewsRepository.count()); ми гърми с : org.opentest4j.AssertionFailedError:
Expected :1
Actual :0 Репозиторито изобщо даже не мога да го напълня с обекта. Не знам даже дали така е правилно да го пълня, понеже нали е кух обект и не съм сигурен дали не трябва да го науча и на вградените му методи като mockNewsRepository.save(newsTest); Може да ползвате горните линкове, които поискахте да изпратя
Здравей!
Mock-a е кух, нищо правещ обект. Това не е истинско репозитори. Представи си приблизително така. Имаш един интерфейс, който изглежда така:
interface StudentIfc {
int getGrade();
Optional<Integer> findGradeForYear();
boolean isNewStudent();
String getName();
}
@ExtendWith(MockitoExtension.class)
public class StudentServiceTest {
@Mock
StudentIfc mockStudent;
}
мокито ще "произведе" за теб горе долу следния студент:
class MockStudent implements StudentIfc {
@Override
public int getGrade() {
return 0;
}
@Override
public Optional<Integer> findGradeForYear() {
return Optional.empty();
}
@Override
public boolean isNewStudent() {
return false;
}
@Override
public String getName() {
return null;
}
}
Тове е мок. Mockito може да ти помогне да "обучиш" този обект. Например да му кажеш:
when(mockStudent.getName()).thenReturn("Pesho");
Тогава мокито ще се погрижи горният метод да стане такъв:
@Override
public String getName() {
return "Pesho";
}
Като правиш Unit test-ове, всичко работи по този начин. Затова няма начин да "напълниш" репозиторито в unit test. То не е вързано с база. То е кухо нещо, което ти можеш да обучиш или пък да провериш кои негови методи с какво са извикани.
Надявам се това обяснява, защо горния тест не работи.
Поздрави,
Л.
Тоест ако трябва да съхранявам в репозиторито обект трябва да е when(mockrepository.save(object)).thenReturn(Object). Защо не мога да съхраня обект в репозиторито, къде бъркам, господин Балев ? Например тук репозиторито ми остава празно :
@test
public void deleteByIdTest() {
newsTest.setId(1L);
newsTest.setDescription(NEWS_DESCRIPTION);
newsTest.setTitle(NEWS_TITLE);
newsTest.setImageUrl(NEWS_IMAGE_URL);
newsTest.setFromDate(LocalDate.parse(NEWS_DATE));
mockNewsRepository.save(newsTest);
Assertions.assertEquals(1, mockNewsRepository.count());
testNSI.deleteById(1L);
Assertions.assertEquals(0, mockNewsRepository.count());
}
Не мога да вкарам обект в него. Изглежда, че само така мога да премина deleteById метода :
@test
public void testDeleteById() {
Long id1 = 1L;
Long id2 = 2L;
Long id3 = 3L;
testNSI.deleteById(id1);
testNSI.deleteById(id2);
testNSI.deleteById(id3);
verify(mockNewsRepository, times(1)).deleteById(id1);
verify(mockNewsRepository, times(1)).deleteById(id2);
verify(mockNewsRepository, times(1)).deleteById(id3);
}
Зравей!
Защо не мога да съхраня обект в репозиторито, къде бъркам
Бъркаш в това, че това не е репозитори. Прочети ми коментара още веднъж. Това е mockRepository и ако искаш ще ти покажа имплементацията на save метода. Тя е ето такава:
void save(Entity e) {
}
Както виждаш, никога няма да го напълниш. Когато работиш с мок можеш да правиш следните неща:
- Да обучиш мока да връща определена стойност при определени аргументи. Например:
Mockito.when(aMock.getById(1)).thenReturn(anObject)
- Да провериш дали някой (например сървиса ти) е извикал някой метод на мока (напр мок репозиторито). Например:
verify(mockNewsRepository, times(1)).deleteById(id1);
- Да видиш провериш с какъв аргумент е извикан метод мока. За това се използва ArgumentCaptor.
Когато пишеш тестове с мок трябва да имаш ясната идея какво трябва да прави теста. Например:
//arrange - създаваш някакви обекти и евентуално обучаваш моковете, които са депендънси на сървиса
//act - вика се метод на сървиса с някакви аргументи
//assert - проверява се дали върната стойност е коректна и дали моковете, които са депендънси са извикани правилно.
Например:
// arrange - подготвяме някаква тестова стойност
Long id = 1;
//act - викаме сървиса
testNSI.deleteById(id1);
//assert - проверяваме че сървиса е извикал правилния метод на мок депендънсито правилен брой пъти
verify(mockNewsRepository, times(1)).deleteById(id1);
Ако искаш да пълниш репозиторита, които са истински тогава ти е необходим интегрейшън тест.
Поздрави,
Л.