package pl.mielecmichal.news.services.news;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import pl.mielecmichal.news.entities.news.News;
import pl.mielecmichal.news.entities.newssources.NewsSource;
import pl.mielecmichal.news.repositories.news.NewsRepository;

import static java.util.Arrays.asList;
import static org.mockito.Mockito.*;

import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class NewsServiceTest {

    NewsService newsService;

    NewsRepository newsRepository;

    private static final String FIRST_AUTHOR = "first@mail.com";
    private static final String FIRST_TITLE = "First Title";
    private static final String FIRST_CONTENT = "First Content Content Content";
    private static final String FIRST_URL = "http://localhost/first";

    private static final String SECOND_AUTHOR = "second@mail.com";
    private static final String SECOND_TITLE = "Second";
    private static final String SECOND_CONTENT = "Second Content";

    private static final String THIRD_AUTHOR = "third@mail.com";
    private static final String THIRD_TITLE = "Third Title";
    private static final String THIRD_CONTENT = "Third Content";

    private final News firstNews = firstCorrectNews();
    private final News secondNews = secondCorrectNews();
    private final News thirdNews = thirdCorrectNews();
    private final NewsSource source = correctSource();

    public NewsServiceTest() throws MalformedURLException {

    }

    @Before
    public void setUp() throws MalformedURLException {
        newsRepository = mock(NewsRepository.class);
        newsService = new NewsService(newsRepository);
    }

    @Test
    public void saveNewNewses_savedNewsesGivenAgain_shouldSaveOnlyNew() {
        // given
        List newses = new ArrayList<>();
        newses.add(firstNews);
        newses.add(secondNews);

        when(newsRepository.countByNewsSourceAndAuthorAndTitle(source, FIRST_AUTHOR, FIRST_TITLE)).thenReturn(0L);
        when(newsRepository.countByNewsSourceAndAuthorAndTitle(source, SECOND_AUTHOR, SECOND_TITLE)).thenReturn(1L);

        // when
        newsService.saveNewNewses(newses);

        // then
        verify(newsRepository, times(1)).save(asList(firstNews));
        verify(newsRepository, never()).save(newses);
    }

    private News firstCorrectNews() {
        News news = new News();
        news.setAuthor(FIRST_AUTHOR);
        news.setTitle(FIRST_TITLE);
        news.setContent(FIRST_CONTENT);
        news.setNewsSource(source);
        return news;
    }

    private News secondCorrectNews() {
        News news = new News();
        news.setAuthor(SECOND_AUTHOR);
        news.setTitle(SECOND_TITLE);
        news.setContent(SECOND_CONTENT);
        news.setNewsSource(source);
        return news;
    }

    private News thirdCorrectNews() {
        News news = new News();
        news.setAuthor(THIRD_AUTHOR);
        news.setTitle(THIRD_TITLE);
        news.setContent(THIRD_CONTENT);
        news.setNewsSource(source);
        return news;
    }

    private NewsSource correctSource() throws MalformedURLException {
        NewsSource source = new NewsSource();
        source.setUrl(new URL(FIRST_URL));
        source.setUpdateTime(LocalDateTime.now());
        return source;
    }

}

Я проверил в отладчике, и метод countBy всегда возвращает O, но аргументы в моем SUT другие и правильные. Похоже, Мокито не различать аргументы метода. Ура!

Я добавляю полный исходный код, чтобы показать правильность констант.

Michał Mielec

Ответов: 2

Ответы (2)

Хотя проблема в основном заключается в упорядочивании полей, вы можете сделать несколько вещей, чтобы уменьшить вероятность повторения этой ошибки и немного улучшить свой тест.

Прежде всего, ваши три метода - firstCorrectNews, secondCorrectNewsи thirdCorrectNews - все делают одно и то же с немного разными параметрами. Имеет смысл объединить их назначение.

private News correctNews(final String author, final String title, final String content, final NewsSource source) {
    final News news = new News();
    news.setAuthor(author);
    news.setTitle(title);
    news.setContent(content);
    news.setNewsSource(source);
    return news;
}

Если вы используете этот метод для начальной загрузки ваших тестовых объектов news, вы будете вынуждены каждый раз передавать источник, чтобы вы не попали в ловушку чего-либо, что зависит от состояния вашего тестового объекта в целом.

Пока мы здесь, стоит также изменить метод correctiveSource, чтобы мы передавали URL-адрес вместо повторного присвоения состояния.

private NewsSource correctSource(final String url) throws MalformedURLException {
    final NewsSource source = new NewsSource();
    source.setUrl(new URL(url));
    source.setUpdateTime(LocalDateTime.now());
    return source;
}

Затем мы можем использовать класс runner Mockito, чтобы нам не приходилось обновлять макеты в предложении @ Before. Это делает код намного меньше и оправдывает ожидания того, что представляют собой фиктивные классы и тестовый класс.

@RunWith(MockitoJUnitRunner.class)
public class NewsServiceTest {

    @Mock
    NewsRepository newsRepository;

    @InjectMocks
    NewsService newsService;

    // other code to follow

}

А теперь давайте все вместе. Этот должен быть тем же самым, что вы тестируете, с основными отличиями:

  • В вашем коде намного меньше повторений, особенно при начальной загрузке ожидаемых данных
  • Ваши моки четко определены
  • Все тестовые данные, которые вам действительно нужны, находятся в вашем конкретном тесте, что помогает, когда вы хотите написать больше тестов
  • Ваши тестовые данные также изолированы в области видимости, что позволяет вам запускать этот тест параллельно


@RunWith(MockitoJUnitRunner.class)
public class NewsServiceTest {

    @Mock
    NewsRepository newsRepository;

    @InjectMocks
    NewsService newsService;

    @Test
    public void saveNewNewses_savedNewsesGivenAgain_shouldSaveOnlyNew() {
        // given
        final String FIRST_AUTHOR = "first@mail.com";
        final String FIRST_TITLE = "First Title";
        final String FIRST_CONTENT = "First Content Content Content";
        final String URL = "http://localhost/first";
        final String SECOND_AUTHOR = "second@mail.com";
        final String SECOND_TITLE = "Second";
        final String SECOND_CONTENT = "Second Content";

        final List newses = new ArrayList<>();

        final NewsSource newsSource = correctSource(URL);
        final News firstNews = correctNews(FIRST_AUTHOR, FIRST_TITLE, FIRST_CONTENT, newsSource);
        final News secondNews = correctNews(SECOND_AUTHOR, SECOND_TITLE, SECOND_CONTENT, newsSource);

        newses.add(firstNews);
        newses.add(secondNews);

        // when

        when(newsRepository.countByNewsSourceAndAuthorAndTitle(newsSource, FIRST_AUTHOR, FIRST_TITLE)).thenReturn(0L);
        when(newsRepository.countByNewsSourceAndAuthorAndTitle(newsSource, SECOND_AUTHOR, SECOND_TITLE)).thenReturn(1L);
        newsService.saveNewNewses(newses);

        // then
        verify(newsRepository).save(asList(firstNews));
        verify(newsRepository, never()).save(newses);
    }

    private News correctNews(final String author, final String title, final String content, final NewsSource source) {
        final News news = new News();
        news.setAuthor(author);
        news.setTitle(title);
        news.setContent(content);
        news.setNewsSource(source);
        return news;
    }


    private NewsSource correctSource(final String url) throws MalformedURLException {
        NewsSource source = new NewsSource();
        source.setUrl(new URL(url));
        source.setUpdateTime(LocalDateTime.now());
        return source;
    }
}

Хорошо, проблема в порядке инициализации новостей и исходников.

Когда я создаю firstNews, secondNews, thirdNews, исходный объект еще не имеет значения. Но внутри моего теста он полностью инициализирован.

private final NewsSource source = correctSource(); //should be here
private final News firstNews = firstCorrectNews();
private final News secondNews = secondCorrectNews();
private final News thirdNews = thirdCorrectNews();
private final NewsSource source = correctSource(); //not here

2022 WebDevInsider