[Перевод] Сравнение SpringRunner со SpringExtension и @SpringBootTest
Команда Spring АйО перевела статью о том, как и когда использовать SpringRunner
, SpringExtension
и @SpringBootTest
, когда их целесообразно комбинировать и как правильное понимание этих компонентов может помочь сделать тесты проще, быстрее и более узконаправленными.
Эффективное тестирование Spring Boot приложений требует понимания тестовой инфраструктуры, которую предлагает Spring.
Три часто встречающихся компонента — SpringRunner
, SpringExtension
и @SpringBootTest
— часто запутывают разработчиков. Многие просто вставляют эти аннотации куда попало, не понимая их предназначения и как именно они дополняют друг друга.
В этой статье мы разъясним различия между этими тестовыми компонентами, разберем, когда используется каждый из них и продемонстрируем, как они работают вместе. По итогу у нас появится четкое понимание тестовой экосистемы Spring, и мы сможем писать более эффективные тесты для наших приложений на Spring Boot.
Эволюция JUnit: SpringRunner и SpringExtension
Первым запутанным моментом часто оказывается то, в каких отношениях находятся между собой SpringRunner
и SpringExtension
.
Давайте объясним это, обратившись к предыстории и приведя несколько примеров кода.
SpringRunner — подход, как в JUnit 4
SpringRunner предназначен для запуска тестов в JUnit 4, который строит мост между Spring и фреймворком для запуска тестов из JUnit 4.
Это алиас для SpringJUnit4ClassRunner
, который предоставляет основную функциональность, необходимую для запуска тестов, которые используют тестовые возможности из Spring.
Приведем пример тестового класса JUnit 4, использующего SpringRunner
:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceJUnit4Test {
@Autowired
private UserService userService;
@Test
public void testUserCreation() {
User user = new User("test@example.com", "password");
User savedUser = userService.createUser(user);
assertNotNull(savedUser.getId());
assertEquals("test@example.com", savedUser.getEmail());
}
}
Аннотация @RunWith(SpringRunner.class)
предписывает JUnit 4 использовать функциональность поддержки тестов от Spring.
Она отвечает за создание контекста приложения, разрешение инжекции зависимостей и управление событиями жизненного цикла теста.
SpringExtension — подход, как в JUnit 5
В JUnit 5 (Jupiter) модель расширения (extension) заменила собой концепцию запускающего тест класса (runner), свойственную JUnit 4. SpringExtension
является реализацией этой новой модели расширения, предлагаемой Spring и предоставляющей нам ту же функциональность, что и у SpringRunner
, но для JUnit 5.
Далее приводится тот же тест, что и выше, но переписанный для JUnit 5 с использованием SpringExtension
:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ExtendWith(SpringExtension.class)
@SpringBootTest
class UserServiceJUnit5Test {
@Autowired
private UserService userService;
@Test
void testUserCreation() {
User user = new User("test@example.com", "password");
User savedUser = userService.createUser(user);
assertNotNull(savedUser.getId());
assertEquals("test@example.com", savedUser.getEmail());
}
}
Ключевая разница состоит в использовании @ExtendWith(SpringExtension.class)
вместо @RunWith(SpringRunner.class)
. Обе аннотации служат выполнению одной и той же цели: интегрируют тестовую инфраструктуру Spring с соответствующей версией JUnit.
Понимание @SpringBootTest
В то время как SpringRunner
и SpringExtension
предоставляют интеграцию между Spring и JUnit, @SpringBootTest
определяет, что и как тестировать. Эта аннотация конфигурирует контекст приложения на Spring для наших тестов, загружая полную конфигурацию приложения.
Аннотация @SpringBootTest
предлагает различные свойства для кастомизации нашего тестового окружения:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"spring.datasource.url=jdbc:h2:mem:testdb"}
)
@ActiveProfiles("test")
class ConfiguredApplicationTest {
@Autowired
private UserRepository userRepository;
@Test
void testUserRepository() {
userRepository.save(new User("admin@example.com", "admin"));
User foundUser = userRepository.findByEmail("admin@example.com").orElse(null);
assertEquals("admin@example.com", foundUser.getEmail());
}
}
В этом примере:
Мы задаем
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
, чтобы запустить встроенный сервер со случайным портомМы перезаписываем свойство с помощью
properties = {"spring.datasource.url=jdbc:h2:mem:testdb"}
Мы активируем профиль «test» с помощью
@ActiveProfiles("test")
Эти конфигурации позволяют нам идеально контролировать наше тестовое окружение, не меняя код приложения.
Как комбинировать компоненты: Best Practices
Теперь когда мы понимаем каждый компонент в отдельности, давайте разберемся, как они работают вместе и какие best practices применимы к различным тестовым сценариям.
JUnit 5: упрощенная конфигурация
В Spring Boot 2.2.0 и позднее в JUnit 5 аннотация @SpringBootTest
включает @ExtendWith(SpringExtension.class)
по умолчанию, позволяя нам упростить код нашего теста:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest
class SimplifiedUserServiceTest {
@Autowired
private UserService userService;
@Test
void testUserCreation() {
User user = new User("test@example.com", "password");
User savedUser = userService.createUser(user);
assertNotNull(savedUser.getId());
assertEquals("test@example.com", savedUser.getEmail());
}
}
Этот упрощенный подход рекомендуется для тестов на JUnit 5. Однако, если мы используем JUnit 4, нам все еще необходимо включать @RunWith(SpringRunner.class)
в явном виде.
Выбор правильного охвата для теста
В то время как @SpringBootTest
загружает полный контекст приложения, иногда мы хотим написать более узконаправленный тест. Spring предлагает дополнительные тестовые аннотации для различных охватов:
Тестирование веб слоя с помощью MockMvc
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testCreateUser() throws Exception {
User mockUser = new User("test@example.com", "password");
mockUser.setId(1L);
when(userService.createUser(any(User.class))).thenReturn(mockUser);
mockMvc.perform(post("/api/users")
.contentType("application/json")
.content("{\"email\":\"test@example.com\",\"password\":\"password\"}"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
}
@WebMvcTest
фокусируется на тестировании только веб слоя и неявно включает SpringExtension
. Она быстрее @SpringBootTest
, потому что загружает только те компоненты, которые относятся к веб.
Тестирование слоя данных
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void testFindByEmail() {
User user = new User("test@example.com", "password");
userRepository.save(user);
assertTrue(userRepository.findByEmail("test@example.com").isPresent());
assertEquals("test@example.com", userRepository.findByEmail("test@example.com").get().getEmail());
}
}
@DataJpaTest
фокусируется на тестировании JPA компонентов, конфигурирует in-memory базу данных и включает только относящиеся к JPA бины. Как и @WebMvcTest
, она неявно включает SpringExtension
.
Заключение
Понимание различий между SpringRunner
, SpringExtension
и @SpringBootTest
помогает нам писать более эффективные тесты для приложений на Spring Boot:
SpringRunner
предназначен для JUnit 4 и интегрирует возможности Spring в области тестирования с моделью запускающего класса JUnitSpringExtension
предназначен для JUnit 5 и предоставляет эквивалентную функциональность, используя модель расширения от Jupiter@SpringBootTest
конфигурирует контекст приложения для тестов и может быть кастомизирована с помощью различных свойств
Используя Spring Boot 2.2.0+ и JUnit 5, мы можем упростить наши тесты, просто используя @SpringBootTest
и не добавляя @ExtendWith(SpringExtension.class)
в явном виде. Для JUnit 4 нам все еще нужна аннотация @RunWith(SpringRunner.class)
.
Выбирая правильную область охвата теста — будет ли это тест всего приложения, выполняемый с помощью @SpringBootTest
, тест веб-слоя, использующий @WebMvcTest
или тест слоя данных с @DataJpaTest
— мы можем делать наши тесты более сфокусированными, быстрыми и простыми в поддержке.
Помните, что наша цель состоит в том, чтобы эффективно тестировать наши приложения, при этом сохраняя простоту и читаемость тестов. Фреймворк для тестирования от Spring предоставляет инструменты, которые пригодятся нам, чтобы достичь такого баланса.
Приятного тестирования.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.