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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Аргумент-матчеры и частичное мокирование

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