Java for Beginner
675 subscribers
549 photos
156 videos
12 files
842 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Введение в тестирование в Spring

Основы тестирования

Тестирование — это ключевой этап разработки, позволяющий удостовериться в правильности работы приложения. Без качественных тестов сложно поддерживать стабильность приложения, особенно с ростом функциональности. Рассмотрим основные понятия тестирования и их важность.

Виды тестирования

Юнит-тесты (Unit Tests):
Тестируют отдельные модули или компоненты приложения, такие как методы или классы.
Основная цель — проверка логики отдельно взятого компонента.
Пример: Проверка корректности метода расчёта.


Интеграционные тесты (Integration Tests):
Проверяют взаимодействие между различными компонентами приложения (например, контроллер ↔️ сервис ↔️ база данных).
Цель — убедиться, что компоненты работают совместно.


Функциональные тесты (Functional Tests):
Проверяют функциональность приложения с точки зрения пользователя.
Пример: Убедиться, что REST API возвращает ожидаемые данные.


Энд-ту-энд тесты (E2E Tests):
Покрывают весь процесс использования приложения, от начала до конца.
Используются для проверки полного пользовательского сценария.


Пирамида тестирования

В идеале, проект должен иметь:
Много юнит-тестов.
Умеренное количество интеграционных тестов.
Немного функциональных и E2E тестов.
Это помогает балансировать между скоростью тестирования и его надежностью.


Инструменты тестирования

Spring предоставляет мощный набор инструментов для тестирования. Ключевые библиотеки и фреймворки:

JUnit 5 (Jupiter):
Основной фреймворк для написания юнит-тестов в Java.
Обеспечивает аннотации для написания и управления тестами.


Mockito:
Используется для создания моков (объектов-заглушек), которые эмулируют поведение реальных зависимостей.
Упрощает тестирование компонентов с зависимостями.


Spring Test:
Расширяет возможности JUnit и Mockito для работы с контекстом Spring.
Позволяет тестировать компоненты Spring (например, бины, репозитории, сервисы) в интеграционном контексте.


#Java #Training #Spring #Testing
Написание первого теста

Рассмотрим пример класса, который нужно протестировать:
public class Calculator {
public int add(int a, int b) {
return a + b;
}

public int subtract(int a, int b) {
return a - b;
}
}


Теперь напишем тест для метода add:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class CalculatorTest {

@Test
void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(5, 3);
Assertions.assertEquals(8, result, "Addition should return correct result");
}
}


Разбор кода

Ключевые элементы:
@Test — помечает метод как тестовый.
Assertions.assertEquals — проверяет, что результат равен ожидаемому значению.

Результат:
Если метод возвращает 8, тест пройдет успешно.
Если результат другой, будет выдано сообщение об ошибке.


Это элементарный тест который показывает насколько одновременно проста и важна сфера тестирования приложения.

#Java #Training #Spring #Testing
Аннотации JUnit 5

Структура JUnit 5

JUnit 5 (также известный как Jupiter) предоставляет богатый набор аннотаций для управления тестами. Каждая из аннотаций служит определенной цели — от настройки окружения до группировки и выполнения тестов.

Состоит из следующих модулей:

JUnit Platform: Основная платформа для запуска тестов.
JUnit Jupiter: Новый API для написания тестов.
JUnit Vintage: Обеспечивает совместимость с JUnit 4.



Аннотации для организации тестов

@Test

Используется для обозначения метода как тестового.
Этот метод должен быть public или default.


Пример:
@Test
void testAddition() {
Assertions.assertEquals(5, 2 + 3);
}


@DisplayName

Устанавливает читаемое имя для теста, которое будет отображаться в отчетах.

Пример:
@Test
@DisplayName("Тест сложения двух чисел")
void testAddition() {
Assertions.assertEquals(5, 2 + 3);
}


@Disabled

Отключает тест, чтобы он не выполнялся.
Можно указать причину отключения.


Пример:
@Test
@Disabled("Тест временно отключен из-за изменений в логике")
void testDisabled() {
Assertions.fail("Этот тест не должен выполняться");
}


Аннотации для управления жизненным циклом тестов


@BeforeEach

Выполняется перед каждым тестом.
Используется для подготовки окружения.


Пример:
@BeforeEach
void setup() {
System.out.println("Подготовка перед тестом");
}


@AfterEach

Выполняется после каждого теста.
Используется для очистки ресурсов.


Пример:
@AfterEach
void cleanup() {
System.out.println("Очистка после теста");
}


@BeforeAll

Выполняется один раз перед всеми тестами в классе.
Метод должен быть static.


Пример:
@BeforeAll
static void globalSetup() {
System.out.println("Настройка перед всеми тестами");
}


@AfterAll

Выполняется один раз после всех тестов в классе.
Метод должен быть static.


Пример:
@AfterAll
static void globalCleanup() {
System.out.println("Очистка после всех тестов");
}


#Java #Training #Spring #Testing #JUnit_5
Продвинутые аннотации JUnit 5

Управление параметрами и контекстами

@Nested

Позволяет группировать связанные тесты в отдельные внутренние классы.

Пример:

@Nested
class MathTests {
@Test
void testAddition() {
Assertions.assertEquals(5, 2 + 3);
}

@Test
void testSubtraction() {
Assertions.assertEquals(1, 3 - 2);
}
}


@ParameterizedTest

Используется для создания параметрических тестов.
Тест выполняется несколько раз с разными значениями.


Пример:
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void testWithParameters(int value) {
Assertions.assertTrue(value > 0);
}


@ValueSource

Определяет набор значений для параметрических тестов.

Пример:
@ParameterizedTest
@ValueSource(strings = {"Hello", "JUnit", "Test"})
void testStrings(String word) {
Assertions.assertFalse(word.isEmpty());
}


@CsvSource

Передает значения в виде CSV (Comma-Separated Values).

Пример:
@ParameterizedTest
@CsvSource({"1,2,3", "2,3,5", "3,5,8"})
void testCsvSource(int a, int b, int result) {
Assertions.assertEquals(result, a + b);
}


@EnumSource

Используется для автоматического предоставления значений из перечислений (enums) в тестовые методы.

Пример:
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

@ParameterizedTest
@EnumSource(Day.class) // Все значения перечисления Day
void testWithEnumSource(Day day) {
Assertions.assertNotNull(day, "Day should not be null");
}


@CsvFileSource

Загружает данные из CSV-файла.

Пример:
@ParameterizedTest
@CsvFileSource(resources = "/data.csv", numLinesToSkip = 1)
void testCsvFileSource(int a, int b, int result) {
Assertions.assertEquals(result, a + b);
}


Работа с условиями

@EnabledOnOs / @DisabledOnOs

Включает или отключает тест на основе операционной системы.

Пример:
@Test
@EnabledOnOs(OS.WINDOWS)
void testWindowsOnly() {
Assertions.assertTrue(System.getProperty("os.name").contains("Windows"));
}


@EnabledIf / @DisabledIf

Условное выполнение тестов.

Тестирование исключений и времени

@Timeout

Устанавливает максимальное время выполнения теста.

Пример:
@Test
@Timeout(5) // Тест завершится с ошибкой, если выполнится дольше 5 секунд
void testTimeout() {
Thread.sleep(3000);
}


@TestFactory

Используется для создания динамических тестов.

Пример:
@TestFactory
Collection<DynamicTest> dynamicTests() {
return Arrays.asList(
DynamicTest.dynamicTest("1st test", () -> Assertions.assertTrue(true)),
DynamicTest.dynamicTest("2nd test", () -> Assertions.assertEquals(4, 2 * 2))
);
}


#Java #Training #Spring #Testing #JUnit_5
Введение в Mockito

Mockito – это популярная библиотека для создания mock-объектов в тестировании. Она позволяет симулировать поведение объектов, чтобы изолировать тестируемую часть кода. Важное преимущество Mockito – это интеграция со Spring, благодаря которой библиотека легко используется в проектах без дополнительной настройки. Достаточно подключить зависимость в проекте.

Основные концепции Mockito

1. Создание моков

Чтобы создать mock-объект, используется метод Mockito.mock(Class). Этот объект ведёт себя как заглушка: он не выполняет реальной логики, но может возвращать заданные вами значения.

Пример:
List<String> mockList = Mockito.mock(List.class);
Mockito.when(mockList.size()).thenReturn(10);
System.out.println(mockList.size()); // Выведет: 10


2. Аннотация @Mock

Аннотация @Mock упрощает создание mock-объектов. Вместо ручного вызова Mockito.mock(), вы просто добавляете эту аннотацию над полем.

Пример:
@Mock
List<String> mockList;


⚠️ Чтобы аннотация заработала, используйте @ExtendWith(MockitoExtension.class) (в JUnit 5) или вызовите MockitoAnnotations.openMocks(this) в JUnit 4.

Stub и возвращение значений

Stub – это настройка поведения mock-объекта. Вы указываете, какое значение должен возвращать mock при вызове его методов. В Mockito для этого используются методы when и thenReturn или thenThrow.

Пример:
// Создание поведения для метода
Mockito.when(mockList.get(0)).thenReturn("Hello, Mockito!");
Mockito.when(mockList.get(1)).thenThrow(new RuntimeException("Ошибка"));

// Проверка
System.out.println(mockList.get(0)); // Выведет: Hello, Mockito!
System.out.println(mockList.get(1)); // Бросит исключение


Преимущества использования Mockito в Spring

Mockito прекрасно сочетается со Spring. Например:
Нет необходимости в явной настройке.
Использование моков через аннотации (@Mock, @InjectMocks) позволяет минимизировать код.
Легкая интеграция с контекстом Spring для тестирования бинов.


#Java #Training #Spring #Testing #Mockito
Проверка вызовов методов с помощью verify в Mockito

Mockito позволяет убедиться, что методы ваших mock-объектов вызывались определённое количество раз, в правильной последовательности или с определёнными параметрами. Это называется проверка взаимодействий.

Как работает verify?
List<String> mockList = Mockito.mock(List.class);

// Взаимодействие
mockList.add("Mockito");
mockList.clear();

// Проверка взаимодействий
Mockito.verify(mockList).add("Mockito"); // Проверяет вызов add с параметром "Mockito"
Mockito.verify(mockList).clear(); // Проверяет вызов clear


Если метод не был вызван, тест упадёт с ошибкой.

Расширенные проверки

Количество вызовов:
Mockito.verify(mockList, Mockito.times(1)).add("Mockito");
Mockito.verify(mockList, Mockito.never()).add("Spring");


Последовательность вызовов:
InOrder inOrder = Mockito.inOrder(mockList);
inOrder.verify(mockList).add("Mockito");
inOrder.verify(mockList).clear();


Проверка отсутствия взаимодействий:
Mockito.verifyNoInteractions(mockList);


Пример: Тестирование сервиса с использованием Mockito

Рассмотрим сервис, который взаимодействует с репозиторием:
@Service
public class UserService {
private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

public String getUserName(Long id) {
return userRepository.findById(id).orElse("Unknown");
}
}


Тест для этого сервиса:
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

@Mock
private UserRepository userRepository;

@InjectMocks
private UserService userService;

@Test
void getUserName_ReturnsCorrectName() {
// Stub репозитория
Mockito.when(userRepository.findById(1L)).thenReturn(Optional.of("John Doe"));

// Вызов метода
String result = userService.getUserName(1L);

// Проверки
Assertions.assertEquals("John Doe", result);
Mockito.verify(userRepository).findById(1L);
}

@Test
void getUserName_ReturnsUnknownForMissingUser() {
// Stub для случая, когда пользователь отсутствует
Mockito.when(userRepository.findById(2L)).thenReturn(Optional.empty());

// Вызов метода
String result = userService.getUserName(2L);

// Проверки
Assertions.assertEquals("Unknown", result);
Mockito.verify(userRepository).findById(2L);
}
}


Что мы сделали в тесте?


Создали mock для UserRepository с помощью @Mock.
Связали mock с тестируемым сервисом через
@InjectMocks.
Задали поведение mock-объекта с помощью when и проверили результат.
Убедились, что репозиторий вызван с нужными параметрами через verify.


#Java #Training #Spring #Testing #Mockito #Verify
Аргумент-матчеры и частичное мокирование

Mockito не ограничивается базовыми функциями вроде создания моков или проверки вызовов. Продвинутые возможности библиотеки позволяют обрабатывать сложные сценарии, такие как использование аргумент-матчеров, частичное мокирование и контроль порядка вызовов.

1. Аргумент-матчеры

Аргумент-матчеры позволяют проверять вызовы методов, не завися от конкретных значений параметров. Это полезно, если параметры варьируются или неизвестны заранее.

Использование стандартных матчеров:

any – принимает любое значение:
Mockito.when(mockList.get(anyInt())).thenReturn("Default");
System.out.println(mockList.get(0)); // Выведет: Default
System.out.println(mockList.get(999)); // Выведет: Default


eq – проверяет точное соответствие:

Mockito.when(mockList.get(eq(1))).thenReturn("Specific");
System.out.println(mockList.get(1)); // Выведет: Specific
System.out.println(mockList.get(2)); // Выведет: null


argThat – настраиваемые условия:
Mockito.when(mockList.get(argThat(i -> i % 2 == 0))).thenReturn("Even");
System.out.println(mockList.get(2)); // Выведет: Even
System.out.println(mockList.get(3)); // Выведет: null


2. Частичное мокирование с помощью spy

Иногда требуется протестировать объект, сохранив его реальные методы, но с возможностью переопределения некоторых из них. Для этого используется spy.

Пример использования spy:
List<String> realList = new ArrayList<>();
List<String> spyList = Mockito.spy(realList);

// Переопределяем метод
Mockito.when(spyList.size()).thenReturn(100);

// Реальные методы продолжают работать
spyList.add("Item");
System.out.println(spyList.get(0)); // Выведет: Item
System.out.println(spyList.size()); // Выведет: 100


Важные моменты:

Используйте spy для реальных объектов, если нужна их логика.
Не смешивайте when и doReturn для одного и того же метода у spy (о doReturn поговорим в следующем посте).


Когда использовать эти подходы?

Аргумент-матчеры полезны для методов с динамическими параметрами.
spy – когда объект имеет сложную логику, которую нельзя полностью заменить mock.


Тест с аргумент-матчерами и spy

Допустим, у нас есть сервис:
@Service
public class CalculatorService {
public int add(int a, int b) {
return a + b;
}

public int multiply(int a, int b) {
return a * b;
}
}


Тест с spy:
@ExtendWith(MockitoExtension.class)
public class CalculatorServiceTest {

@Spy
private CalculatorService calculatorService;

@Test
void testPartialMocking() {
// Частично переопределяем метод
Mockito.when(calculatorService.add(anyInt(), eq(0))).thenReturn(0);

// Проверка
Assertions.assertEquals(0, calculatorService.add(10, 0)); // Переопределено
Assertions.assertEquals(30, calculatorService.add(10, 20)); // Реальная логика

// Проверяем другой метод
Assertions.assertEquals(200, calculatorService.multiply(10, 20));
}
}


#Java #Training #Spring #Testing #Mockito #Spy
Всё о thenReturn, thenThrow и thenAnswer в Mockito

Когда создаются моки с помощью Mockito, вы можете настроить их поведение через методы when и then.... Эти методы позволяют указать, что должен вернуть мок-объект или как он должен себя вести при вызове его методов.

1. thenReturn

thenReturn задаёт значение, которое метод мока должен возвращать при вызове. Это самый простой способ настройки поведения.


Пример:
List<String> mockList = Mockito.mock(List.class);

// Настраиваем поведение метода
Mockito.when(mockList.get(0)).thenReturn("MockedValue");

// Проверяем
System.out.println(mockList.get(0)); // Выведет: MockedValue
System.out.println(mockList.get(1)); // Выведет: null (по умолчанию)


Особенности:
Может возвращать один и тот же результат для любого вызова.
Если метод вызывается с аргументом, для которого не задано поведение, возвращается значение по умолчанию (например, null для объектов, 0 для чисел).


Множественные возвраты:
Вы можете задать несколько возвратов, которые будут использоваться последовательно:
Mockito.when(mockList.get(0))
.thenReturn("FirstCall")
.thenReturn("SecondCall");

System.out.println(mockList.get(0)); // Выведет: FirstCall
System.out.println(mockList.get(0)); // Выведет: SecondCall
System.out.println(mockList.get(0)); // Выведет: SecondCall (последний возврат повторяется)


Когда использовать?
Если поведение метода не меняется в зависимости от параметров.
Для упрощения мокирования статичных данных.


2. thenThrow

thenThrow позволяет настроить метод на выброс исключения при его вызове.

Копировать код
List<String> mockList = Mockito.mock(List.class);

// Настраиваем метод
Mockito.when(mockList.get(1)).thenThrow(new RuntimeException("Error occurred"));

// Попробуем вызвать
try {
mockList.get(1); // Бросит исключение
} catch (RuntimeException e) {
System.out.println(e.getMessage()); // Выведет: Error occurred
}


Множественные исключения:
Как и в случае с thenReturn, вы можете задать несколько последовательных исключений:
Mockito.when(mockList.get(0))
.thenThrow(new IllegalArgumentException("First error"))
.thenThrow(new RuntimeException("Second error"));

try {
mockList.get(0); // Бросит IllegalArgumentException
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // Выведет: First error
}

try {
mockList.get(0); // Бросит RuntimeException
} catch (RuntimeException e) {
System.out.println(e.getMessage()); // Выведет: Second error
}


Когда использовать?
Для тестирования обработки исключений.
Для проверки, как система справляется с ошибками (например, сбои базы данных, ошибки сети).


3. thenAnswer

thenAnswer даёт полный контроль над тем, как метод мока реагирует на вызовы. Вы можете задать сложную логику, которая учитывает переданные параметры или состояние теста.

Пример:
List<String> mockList = Mockito.mock(List.class);

// Настраиваем кастомное поведение
Mockito.when(mockList.get(anyInt())).thenAnswer(invocation -> {
int index = invocation.getArgument(0);
return "Value at index " + index;
});

// Проверяем
System.out.println(mockList.get(0)); // Выведет: Value at index 0
System.out.println(mockList.get(5)); // Выведет: Value at index 5


Особенности:
Полезен для создания динамического поведения.
Работает через интерфейс Answer, который предоставляет доступ к объекту вызова (InvocationOnMock).
Может быть использован для логирования или трассировки вызовов.


Реализация сложной логики
Допустим, вы хотите, чтобы метод мока возвращал разные значения в зависимости от индекса:
Mockito.when(mockList.get(anyInt())).thenAnswer(invocation -> {
int index = invocation.getArgument(0);
if (index % 2 == 0) {
return "Even Index";
} else {
return "Odd Index";
}
});

System.out.println(mockList.get(2)); // Выведет: Even Index
System.out.println(mockList.get(3)); // Выведет: Odd Index


#Java #Training #Spring #Testing #Mockito #thenReturn #thenThrow #thenAnswer
Логирование вызовов:
Mockito.when(mockList.get(anyInt())).thenAnswer(invocation -> {
int index = invocation.getArgument(0);
System.out.println("Method called with index: " + index);
return "Logged Value";
});

System.out.println(mockList.get(10)); // Лог: Method called with index: 10
// Вывод: Logged Value


Полный пример с использованием всех трёх методов

Представим сервис для обработки заказов:
@Service
public class OrderService {
public String createOrder(String productId) {
return "Order created for product: " + productId;
}

public void cancelOrder(String orderId) {
throw new UnsupportedOperationException("Cancel not supported");
}

public String getOrderStatus(String orderId) {
return "Status for order " + orderId;
}
}


Тест:
@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {

@Mock
private OrderService orderService;

@Test
void testThenReturn() {
// Настраиваем thenReturn
Mockito.when(orderService.createOrder("123")).thenReturn("Mocked Order Created");

String result = orderService.createOrder("123");
Assertions.assertEquals("Mocked Order Created", result);
}

@Test
void testThenThrow() {
// Настраиваем thenThrow
Mockito.when(orderService.cancelOrder("invalid")).thenThrow(new IllegalArgumentException("Invalid Order"));

Assertions.assertThrows(IllegalArgumentException.class, () -> {
orderService.cancelOrder("invalid");
});
}

@Test
void testThenAnswer() {
// Настраиваем thenAnswer
Mockito.when(orderService.getOrderStatus(anyString())).thenAnswer(invocation -> {
String orderId = invocation.getArgument(0);
return "Dynamic status for " + orderId;
});

String status = orderService.getOrderStatus("456");
Assertions.assertEquals("Dynamic status for 456", status);
}
}


#Java #Training #Spring #Testing #Mockito #thenReturn #thenThrow #thenAnswer
Тестирование Spring компонентов

Тестирование в Spring предполагает работу как с изолированными компонентами, так и с целым приложением, используя тестовый контекст Spring. Это позволяет проверять, как классы взаимодействуют друг с другом и внешними ресурсами (например, базами данных).

Spring Test Context и аннотации для настройки тестов

1. Подготовка контекста с помощью аннотации
@SpringBootTest

Аннотация
@SpringBootTest используется для запуска полного контекста Spring, который включает все зарегистрированные компоненты, как если бы приложение запускалось в реальной среде.

Пример использования:
@SpringBootTest
public class ApplicationTests {

@Autowired
private MyService myService;

@Test
public void contextLoads() {
Assertions.assertNotNull(myService);
}
}


Основные возможности @SpringBootTest

Запуск полного контекста Spring.
Это включает все бины, аннотации конфигурации, и зависимости.
Подходит для интеграционных тестов или тестирования сложных взаимодействий между компонентами.
Выбор режима загрузки контекста.


@SpringBootTest позволяет управлять, как будет загружаться контекст.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) // Загружается mock-среда
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // Используется реальный сервер с портом


Конфигурация контекста с @ContextConfiguration

Если вы хотите явно указать, какая конфигурация должна быть использована для теста, используйте @ContextConfiguration.

Пример:
@ContextConfiguration(classes = MyConfig.class) // Задаём конкретный класс конфигурации
@SpringBootTest
public class ConfiguredTest {
@Autowired
private MyService myService;

@Test
public void testWithCustomConfig() {
Assertions.assertNotNull(myService);
}
}


2. Использование аннотаций для настройки тестов

Spring предлагает несколько аннотаций, которые помогают настроить тестовый контекст.

1. @MockBean (заменена на @MockitoBean) и @SpyBean

@MockitoBean: Создаёт mock-объекты для использования в контексте Spring.
@SpyBean: Позволяет частично мокировать реальные компоненты.

Пример:
@SpringBootTest
public class ServiceTest {

@MockitoBean
private MyRepository myRepository;

@SpyBean
private MyService myService;

@Test
public void testWithMockBean() {
Mockito.when(myRepository.findById(1L)).thenReturn(Optional.of(new MyEntity(1L, "Test")));

String result = myService.processEntity(1L);
Assertions.assertEquals("Processed: Test", result);
}
}


2. @TestPropertySource
Эта аннотация позволяет переопределить свойства конфигурации приложения для тестов.

Пример:
@TestPropertySource(properties = "spring.datasource.url=jdbc:h2:mem:testdb")
@SpringBootTest
public class PropertySourceTest {

@Autowired
private DataSource dataSource;

@Test
public void testProperties() throws SQLException {
Assertions.assertTrue(dataSource.getConnection().getMetaData().getURL().contains("testdb"));
}
}


3. Дополнительные аннотации
@DirtiesContext: Перезагружает контекст после выполнения теста. Используется, если тест изменяет состояние контекста.
@Transactional: Автоматически откатывает изменения, сделанные в рамках теста, в базу данных.
@Sql и
@SqlGroup: Выполняет SQL-скрипты до или после тестов.

#Java #Training #Spring #Testing #Mockito #SpringBootTest #MockBean #ContextConfiguration
Интеграционные тесты с использованием базы данных

1. Что такое интеграционные тесты?

Интеграционные тесты проверяют работу нескольких компонентов вместе, включая взаимодействие с внешними системами, такими как базы данных, REST API и т. д. Spring предлагает инструменты для упрощения тестирования с использованием реальной базы данных.

2. Интеграционные тесты с PostgreSQL


Чтобы протестировать взаимодействие с базой данных, можно использовать тестовую базу данных (например, H2) или реальную базу данных, такую как PostgreSQL.

Добавьте зависимости: В pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>


Настройте application-test.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=test
spring.datasource.password=test
spring.jpa.hibernate.ddl-auto=update


Пример теста:
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
@Transactional
public class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Test
public void testSaveAndFindUser() {
// Создаём пользователя
User user = new User();
user.setName("John");
user.setEmail("john@example.com");

// Сохраняем пользователя
userRepository.save(user);

// Проверяем, что он сохраняется и извлекается
Optional<User> foundUser = userRepository.findById(user.getId());
Assertions.assertTrue(foundUser.isPresent());
Assertions.assertEquals("John", foundUser.get().getName());
}
}


3. Использование @Sql для предустановки данных

Вы можете использовать SQL-скрипты для подготовки данных перед тестами.

@Sql(scripts = "/test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@SpringBootTest
public class SqlTest {

@Autowired
private UserRepository userRepository;

@Test
public void testWithSqlData() {
Optional<User> user = userRepository.findByName("PreloadedUser");
Assertions.assertTrue(user.isPresent());
}
}


Пример test-data.sql:
INSERT INTO users (id, name, email) VALUES (1, 'PreloadedUser', 'user@example.com');


#Java #Training #Spring #Testing #Mockito #IntegrationTests
Тестирование веб-приложений в Spring

Веб-приложения являются центральным элементом многих проектов, а тестирование REST-контроллеров помогает убедиться, что API работает корректно. Для этих целей Spring предлагает инструменты, такие как MockMvc и аннотация @WebMvcTest.

Основы тестирования контроллеров с MockMvc и @WebMvcTest

1. Использование MockMvc

MockMvc — это инструмент для тестирования REST-контроллеров без необходимости запуска всего приложения.

Он позволяет:
Отправлять HTTP-запросы к контроллерам.
Проверять их ответы (статус, заголовки, тело и т. д.).
Работать с сериализацией/десериализацией JSON.



Создайте тестовый класс с аннотацией @WebMvcTest:
@WebMvcTest(controllers = MyController.class)
public class MyControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private MyService myService;

// Тесты будут здесь
}


2. Аннотация @WebMvcTest

Аннотация
@WebMvcTest конфигурирует Spring так, чтобы загружались только веб-компоненты, связанные с тестируемыми контроллерами. Это делает тесты быстрыми и изолированными.

Особенности:
Загружает только контекст веб-слоя:
Регистрируются только контроллеры, обработчики ошибок и связанные с ними компоненты.
Другие части приложения, такие как сервисы или репозитории, не загружаются.


Использование @MockitoBean:
Для замены зависимостей контроллеров мок-объектами.
Импорт дополнительных компонентов: Если нужно задействовать специфическую конфигурацию, можно использовать @Import.

Пример:
@WebMvcTest(controllers = MyController.class)
@Import(MyCustomConfig.class)
public class MyControllerTest {
// Тесты
}


3. Методы MockMvc: perform, andExpect, andReturn


Метод perform

Отправляет HTTP-запрос к указанному маршруту контроллера. Он принимает объект MockHttpServletRequestBuilder, который позволяет настроить запрос.

Пример GET-запроса:
mockMvc.perform(get("/api/items"))
.andExpect(status().isOk());


Пример POST-запроса:
mockMvc.perform(post("/api/items")
.content("{\"name\":\"Test Item\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated());


Метод andExpect

Проверяет различные аспекты ответа:
HTTP-статус.
Заголовки.
Тело ответа.


Пример:
mockMvc.perform(get("/api/items"))
.andExpect(status().isOk()) // Проверка HTTP-статуса
.andExpect(content().contentType(MediaType.APPLICATION_JSON)) // Проверка заголовка Content-Type
.andExpect(jsonPath("$.size()").value(3)); // Проверка JSON-данных


Метод andReturn

Возвращает полный объект ответа, который можно анализировать для более сложных проверок.

Пример:
MvcResult result = mockMvc.perform(get("/api/items"))
.andExpect(status().isOk())
.andReturn();

String responseBody = result.getResponse().getContentAsString();
Assertions.assertTrue(responseBody.contains("ItemName"));


4. Работа с сериализацией и тестирование JSON-данных

Для тестирования контроллеров, возвращающих JSON, полезно использовать библиотеку Jackson (по умолчанию в Spring Boot).

Пример сериализации объекта в JSON:
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(new Item(1L, "Test Item"));


Пример десериализации JSON в объект:
String jsonResponse = "{\"id\":1,\"name\":\"Test Item\"}";
Item item = objectMapper.readValue(jsonResponse, Item.class);
Assertions.assertEquals("Test Item", item.getName());


#Java #Training #Spring #Testing #Mockito #TestingWeb
Написание тестов для CRUD REST API

Пример контроллера
@RestController
@RequestMapping("/api/items")
public class ItemController {

private final ItemService itemService;

@Autowired
public ItemController(ItemService itemService) {
this.itemService = itemService;
}

@GetMapping
public List<Item> getAllItems() {
return itemService.getAllItems();
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Item createItem(@RequestBody Item item) {
return itemService.saveItem(item);
}

@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteItem(@PathVariable Long id) {
itemService.deleteItem(id);
}
}


Тестирование CRUD операций
@WebMvcTest(controllers = ItemController.class)
public class ItemControllerTest {

@Autowired
private MockMvc mockMvc;

@MockitoBean
private ItemService itemService;

@Test
public void testGetAllItems() throws Exception {
List<Item> items = Arrays.asList(new Item(1L, "Item1"), new Item(2L, "Item2"));
Mockito.when(itemService.getAllItems()).thenReturn(items);

mockMvc.perform(get("/api/items"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.size()").value(2))
.andExpect(jsonPath("$[0].name").value("Item1"));
}

@Test
public void testCreateItem() throws Exception {
Item item = new Item(1L, "NewItem");
Mockito.when(itemService.saveItem(Mockito.any(Item.class))).thenReturn(item);

mockMvc.perform(post("/api/items")
.content("{\"name\":\"NewItem\"}")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("NewItem"));
}

@Test
public void testDeleteItem() throws Exception {
mockMvc.perform(delete("/api/items/1"))
.andExpect(status().isNoContent());
}
}


Особенности

@MockitoBean для сервиса:
Изолирует тест от реального сервиса.
Позволяет задавать поведение с помощью Mockito.


Тестирование сериализации/десериализации:
Используйте ObjectMapper для работы с JSON.

Работа с jsonPath:
Удобный способ проверки JSON-структур.

#Java #Training #Spring #Testing #Mockito #TestingWeb
Основы тестирования взаимодействия с базами данных в Spring

Тестирование взаимодействия с базами данных — важная часть разработки приложений на Spring.

Основные аннотации


@DataJpaTest:
Эта аннотация используется для тестирования JPA-репозиториев. Она настраивает in-memory базу данных (например, H2) по умолчанию, но можно настроить и для работы с PostgreSQL.
Автоматически настраивает EntityManager, DataSource и другие компоненты, необходимые для работы с JPA.

@SpringBootTest:
Используется для интеграционного тестирования, когда нужно поднять весь контекст Spring. Подходит для тестирования взаимодействия с реальной базой данных.
Можно указать webEnvironment = SpringBootTest.WebEnvironment.NONE, чтобы не поднимать веб-сервер.


@TestConfiguration:
Позволяет определить дополнительные бины или конфигурации, которые будут использоваться только в тестах.

@Sql:
Позволяет выполнять SQL-скрипты перед или после тестов. Например, для заполнения базы данных тестовыми данными.

@Transactional:
Указывает, что тест должен выполняться в транзакции, которая будет откачена после завершения теста. Это помогает избежать изменений в базе данных между тестами.

@AutoConfigureTestDatabase:
Позволяет заменить in-memory базу данных на реальную (например, PostgreSQL) для тестирования.

Настройка тестовой базы данных

Для тестирования с PostgreSQL нужно:
Добавить зависимость на PostgreSQL в pom.xml или build.gradle:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>


Настроить application-test.properties или application-test.yml для подключения к PostgreSQL:
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=user
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create-drop


Пример теста
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Sql(scripts = "/init-test-data.sql")
public class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Test
public void testFindByUsername() {
User user = userRepository.findByUsername("testuser");
assertNotNull(user);
assertEquals("testuser", user.getUsername());
}
}


#Java #Training #Spring #Testing #TestingDB
Нюансы и продвинутые сценарии тестирования с PostgreSQL

Нюансы тестирования

Использование транзакций:
Аннотация @Transactional в тестах гарантирует, что изменения в базе данных будут откачены после завершения теста. Это важно для изоляции тестов.
Если нужно проверить поведение без транзакций, можно использовать
@Commit или @Rollback(false).

Инициализация данных

Используйте @Sql для выполнения SQL-скриптов перед тестами. Например:
@Sql(scripts = "/init-test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)


Для очистки данных после теста:
@Sql(scripts = "/cleanup-test-data.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)


Тестирование миграций:
Если вы используете Flyway или Liquibase, убедитесь, что миграции применяются в тестовой базе данных. Для этого можно использовать @SpringBootTest с настройкой spring.flyway.enabled=true.

Тестирование производительности:
Для тестирования производительности запросов можно использовать @Timed или @Repeat для многократного выполнения тестов.

Пример интеграционного теста
@SpringBootTest
@Transactional
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class UserServiceIntegrationTest {

@Autowired
private UserService userService;

@Test
@Sql(scripts = "/init-test-data.sql")
public void testCreateUser() {
User user = new User();
user.setUsername("newuser");
user.setPassword("password");

User savedUser = userService.createUser(user);
assertNotNull(savedUser.getId());
assertEquals("newuser", savedUser.getUsername());
}
}


Тестирование с использованием Testcontainers

Для более реалистичного тестирования можно использовать Testcontainers, который позволяет запускать PostgreSQL в Docker-контейнере.

Добавьте зависимость Testcontainers:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.17.6</version>
<scope>test</scope>
</dependency>


Настройте тест:
@SpringBootTest
@Testcontainers
public class UserServiceTestcontainersTest {

@Container
private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13");

@DynamicPropertySource
static void postgresProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}

@Autowired
private UserService userService;

@Test
public void testCreateUser() {
User user = new User();
user.setUsername("testuser");
user.setPassword("password");

User savedUser = userService.createUser(user);
assertNotNull(savedUser.getId());
assertEquals("testuser", savedUser.getUsername());
}
}


#Java #Training #Spring #Testing #TestingDB
Тестирование сложных сценариев в Spring

Тестирование сложных сценариев в Spring требует понимания работы асинхронного кода, событий и транзакций.

1. Тестирование асинхронного кода

Асинхронные методы, помеченные аннотацией @Async, возвращают CompletableFuture или void. Для их тестирования важно убедиться, что код выполняется в отдельном потоке и завершается корректно.

Пример асинхронного метода:
@Service
public class AsyncService {

@Async
public CompletableFuture<String> asyncMethod() {
// Имитация долгой операции
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return CompletableFuture.completedFuture("Done");
}
}


Тестирование асинхронного метода:

Используйте CountDownLatch для ожидания завершения асинхронной операции.

@SpringBootTest
public class AsyncServiceTest {

@Autowired
private AsyncService asyncService;

@Test
void testAsyncMethod() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<String> future = asyncService.asyncMethod();

future.whenComplete((result, ex) -> {
assertThat(result).isEqualTo("Done");
latch.countDown();
});

latch.await(2, TimeUnit.SECONDS); // Ожидаем завершения
assertThat(future.isDone()).isTrue();
}
}
Здесь CountDownLatch используется для синхронизации теста с асинхронным выполнением.


2. Тестирование событий

Spring предоставляет механизм событий через ApplicationEventPublisher. Тестирование событий включает проверку публикации событий и реакции подписчиков.

Пример публикации события:
@Service
public class EventService {

@Autowired
private ApplicationEventPublisher publisher;

public void publishEvent(String message) {
publisher.publishEvent(new CustomEvent(this, message));
}
}

public class CustomEvent extends ApplicationEvent {
private final String message;

public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}

public String getMessage() {
return message;
}
}


Тестирование публикации события:

Используйте ApplicationEvents (из Spring Test) для проверки событий.

@SpringBootTest
public class EventServiceTest {

@Autowired
private EventService eventService;

@Autowired
private ApplicationEvents events;

@Test
void testEventPublication() {
eventService.publishEvent("Test Message");

// Проверяем, что событие было опубликовано
assertThat(events.stream(CustomEvent.class))
.hasSize(1)
.first()
.satisfies(event -> assertThat(event.getMessage()).isEqualTo("Test Message"));
}
}


3. Тестирование транзакций


Транзакции — это ключевой аспект работы с базами данных. Важно убедиться, что транзакции корректно завершаются (commit или rollback).

Пример транзакционного метода:
@Service
public class TransactionalService {

@Autowired
private UserRepository userRepository;

@Transactional
public void createUser(String name) {
User user = new User(name);
userRepository.save(user);
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
}
}


Тестирование rollback

Проверьте, что транзакция откатывается при ошибке.
@SpringBootTest
public class TransactionalServiceTest {

@Autowired
private TransactionalService transactionalService;

@Autowired
private UserRepository userRepository;

@Test
void testRollback() {
assertThatThrownBy(() -> transactionalService.createUser(null))
.isInstanceOf(IllegalArgumentException.class);

// Проверяем, что пользователь не был сохранен
assertThat(userRepository.findAll()).isEmpty();
}
}


#Java #Training #Spring #Testing #Mockito
Лучшие практики, советы и нюансы тестирования в Spring

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

1. Изолируйте тесты

Каждый тест должен быть независимым и не зависеть от состояния, созданного другими тестами. Это позволяет избежать ложных срабатываний и упрощает отладку.

Используйте `@Transactional`:

Аннотация @Transactional автоматически откатывает изменения в базе данных после завершения теста.

  @Test
@Transactional
void testSaveUser() {
User user = new User("John Doe");
userRepository.save(user);
assertThat(userRepository.findAll()).hasSize(1);
}


Очищайте базу данных:

Если вы не используете @Transactional, очищайте базу данных перед каждым тестом с помощью @Sql или вручную.

  @Test
@Sql("/scripts/cleanup.sql")
void testUserCreation() {
// Тестовый код
}


Используйте правильные типы тестов

Unit-тесты:

Тестируйте отдельные компоненты (например, сервисы или репозитории) в изоляции. Используйте моки для зависимостей.

  @ExtendWith(MockitoExtension.class)
public class UserServiceTest {

@Mock
private UserRepository userRepository;

@InjectMocks
private UserService userService;

@Test
void testFindUserById() {
when(userRepository.findById(1L)).thenReturn(Optional.of(new User("John Doe")));
User user = userService.findUserById(1L);
assertThat(user.getName()).isEqualTo("John Doe");
}
}


Интеграционные тесты:

Тестируйте взаимодействие нескольких компонентов (например, сервисов и репозиториев) вместе. Используйте @SpringBootTest.

  @SpringBootTest
public class UserServiceIntegrationTest {

@Autowired
private UserService userService;

@Test
void testCreateUser() {
User user = userService.createUser("Jane Doe");
assertThat(user).isNotNull();
}
}


Используйте Testcontainers для интеграционных тестов

Testcontainers позволяет запускать реальные базы данных (например, PostgreSQL) в Docker-контейнерах. Это делает тесты более приближенными к production-среде.

- Пример:

  @Testcontainers
@SpringBootTest
public class UserRepositoryTest {

@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13");

@DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}

@Test
void testSaveUser() {
User user = new User("John Doe");
userRepository.save(user);
assertThat(userRepository.findAll()).hasSize(1);
}
}


#Java #Training #Spring #Testing #Mockito
Тестируйте edge-кейсы

Убедитесь, что ваши тесты покрывают не только "счастливый путь", но и граничные случаи:
- Ошибки валидации.
- Отсутствие данных в базе.
- Нарушение уникальности.
- Исключения в транзакциях.


- Пример:

  @Test
void testUserNotFound() {
assertThatThrownBy(() -> userService.findUserById(999L))
.isInstanceOf(UserNotFoundException.class);
}


Используйте моки для сложных зависимостей

Моки позволяют изолировать тестируемый компонент от внешних зависимостей, таких как базы данных, внешние API или другие сервисы.

- Пример с Mockito:

  @Test
void testSendEmail() {
EmailService emailService = mock(EmailService.class);
UserService userService = new UserService(emailService);

userService.notifyUser("user@example.com");

verify(emailService).sendEmail("user@example.com");
}


Тестируйте асинхронный код

Для тестирования асинхронных методов используйте CompletableFuture и CountDownLatch.

- Пример:

  @Test
void testAsyncMethod() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<String> future = asyncService.asyncMethod();

future.whenComplete((result, ex) -> {
assertThat(result).isEqualTo("Done");
latch.countDown();
});

latch.await(2, TimeUnit.SECONDS);
assertThat(future.isDone()).isTrue();
}


Тестируйте события

Spring предоставляет механизм событий через ApplicationEventPublisher. Убедитесь, что события публикуются и обрабатываются корректно.

- Пример:

  @Test
void testEventPublication() {
eventService.publishEvent("Test Message");

assertThat(events.stream(CustomEvent.class))
.hasSize(1)
.first()
.satisfies(event -> assertThat(event.getMessage()).isEqualTo("Test Message"));
}


Оптимизируйте производительность тестов

Минимизируйте контекст Spring:

Используйте
@DataJpaTest, @WebMvcTest и другие специализированные аннотации вместо @SpringBootTest, если это возможно.

Кэшируйте контекст Spring:

Если тесты используют один и тот же контекст, Spring может кэшировать его для ускорения выполнения.

Документируйте тесты

Используйте понятные имена тестов:
Имена тестов должны отражать их цель (например, testCreateUser_withNullName_throwsException).


Добавляйте комментарии:
Если тест сложный, добавьте пояснения, чтобы облегчить понимание.


Автоматизируйте тестирование

Интегрируйте тесты в CI/CD:
Убедитесь, что тесты запускаются автоматически при каждом коммите.

Используйте инструменты для анализа покрытия:
Например, JaCoCo для измерения покрытия кода тестами.

#Java #Training #Spring #Testing #Mockito