Gymterview
junior

Что такое Nested тесты в JUnit 5?

@Nested — аннотация JUnit 5, позволяющая создавать вложенные тестовые классы для логической группировки тестов. Это улучшает структуру и читаемость, особенно когда один класс содержит много тестов для разных сценариев.

Полный пример с Nested тестами
@DisplayName("Тесты UserService")
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserServiceImpl userService;

    @Nested
    @DisplayName("Метод findById")
    class FindById {

        @Test
        @DisplayName("должен вернуть пользователя по существующему id")
        void shouldReturnUserWhenExists() {
            User user = new User(1L, "Иван");
            when(userRepository.findById(1L)).thenReturn(Optional.of(user));

            User result = userService.findById(1L);

            assertEquals("Иван", result.getName());
        }

        @Test
        @DisplayName("должен бросить исключение при несуществующем id")
        void shouldThrowWhenNotFound() {
            when(userRepository.findById(999L)).thenReturn(Optional.empty());

            assertThrows(UserNotFoundException.class,
                () -> userService.findById(999L));
        }
    }

    @Nested
    @DisplayName("Метод create")
    class Create {

        @Test
        @DisplayName("должен создать пользователя с валидными данными")
        void shouldCreateValidUser() {
            UserDto dto = new UserDto("Иван", "ivan@example.com");
            when(userRepository.save(any())).thenAnswer(inv -> {
                User u = inv.getArgument(0);
                u.setId(1L);
                return u;
            });

            User result = userService.create(dto);

            assertNotNull(result.getId());
            verify(userRepository).save(any());
        }

        @Test
        @DisplayName("должен бросить исключение при дублировании email")
        void shouldThrowOnDuplicateEmail() {
            UserDto dto = new UserDto("Иван", "existing@example.com");
            when(userRepository.existsByEmail("existing@example.com"))
                .thenReturn(true);

            assertThrows(DuplicateEmailException.class,
                () -> userService.create(dto));

            verify(userRepository, never()).save(any());
        }
    }
}

Результат в отчёте

Пример
Тесты UserService
├── Метод findById
│   ├── должен вернуть пользователя по существующему id
│   └── должен бросить исключение при несуществующем id
├── Метод create
│   ├── должен создать пользователя с валидными данными
│   └── должен бросить исключение при дублировании email
└── Метод delete
    ├── должен удалить существующего пользователя
    └── должен бросить исключение при удалении несуществующего

Вложенные классы имеют доступ к полям и мокам внешнего класса. При этом @BeforeEach внешнего класса выполняется перед @BeforeEach вложенного.

На собеседовании: покажите, что @Nested используется для организации тестов по тестируемым методам или сценариям. Частая ошибка — забыть, что вложенные классы не могут иметь @BeforeAll/@AfterAll без @TestInstance(Lifecycle.PER_CLASS).