CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework--spring

Comprehensive application framework and inversion of control container for the Java platform providing dependency injection, AOP, data access, transaction management, and web framework capabilities

Overview
Eval results
Files

testing.mddocs/

Testing Support

Spring Test provides comprehensive testing support for Spring applications, including the TestContext Framework, MockMvc for web layer testing, transaction rollback support, and integration with popular testing frameworks like JUnit and TestNG.

Maven Dependencies

<!-- Spring Test -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.39</version>
    <scope>test</scope>
</dependency>

<!-- JUnit 5 -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

<!-- Mockito -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.6.1</version>
    <scope>test</scope>
</dependency>

<!-- AssertJ -->
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.24.2</version>
    <scope>test</scope>
</dependency>

<!-- Spring Boot Test (if using Spring Boot) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Core Imports

// TestContext Framework
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.annotation.DirtiesContext;

// Spring Boot Testing
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;

// MockMvc
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

// WebTestClient (WebFlux)
import org.springframework.test.web.reactive.server.WebTestClient;

// Transaction Testing
import org.springframework.test.annotation.Rollback;
import org.springframework.test.annotation.Commit;
import org.springframework.transaction.annotation.Transactional;

// JUnit 5
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.extension.ExtendWith;

// Assertions
import static org.assertj.core.api.Assertions.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

TestContext Framework

Core Testing Annotations

// Combination of @ExtendWith(SpringExtension.class) and @ContextConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public @interface SpringJUnitConfig {
    @AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
    Class<?>[] value() default {};
    
    @AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
    Class<?>[] classes() default {};
    
    @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
    String[] locations() default {};
}

// Defines class-level metadata for configuring TestContext
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ContextConfiguration {
    @AliasFor("locations")
    String[] value() default {};
    
    String[] locations() default {};
    
    Class<?>[] classes() default {};
    
    Class<? extends ApplicationContextInitializer<?>>[] initializers() default {};
    
    boolean inheritLocations() default true;
    
    boolean inheritInitializers() default true;
    
    String name() default "";
}

// Indicates which active bean definition profiles should be used
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ActiveProfiles {
    @AliasFor("profiles")
    String[] value() default {};
    
    String[] profiles() default {};
    
    boolean inheritProfiles() default true;
    
    Class<? extends ActiveProfilesResolver> resolver() default ActiveProfilesResolver.class;
}

// Configures test property sources
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(TestPropertySources.class)
public @interface TestPropertySource {
    @AliasFor("locations")
    String[] value() default {};
    
    String[] locations() default {};
    
    boolean inheritLocations() default true;
    
    String[] properties() default {};
    
    boolean inheritProperties() default true;
}

// Indicates that ApplicationContext should be closed and removed from cache
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DirtiesContext {
    ScopeMode value() default ScopeMode.DEFAULT_SCOPE;
    
    ClassMode classMode() default ClassMode.AFTER_CLASS;
    
    MethodMode methodMode() default MethodMode.AFTER_METHOD;
    
    Class<? extends HierarchyMode> hierarchyMode() default HierarchyMode.EXHAUSTIVE;
    
    enum ClassMode {
        BEFORE_CLASS, BEFORE_EACH_TEST_METHOD, AFTER_EACH_TEST_METHOD, AFTER_CLASS
    }
    
    enum MethodMode {
        BEFORE_METHOD, AFTER_METHOD
    }
    
    enum HierarchyMode {
        CURRENT_LEVEL, EXHAUSTIVE
    }
}

TestContext Interface

// Encapsulates the context for running a test
public interface TestContext extends AttributeAccessor, Serializable {
    
    ApplicationContext getApplicationContext();
    
    Class<?> getTestClass();
    
    Object getTestInstance();
    
    Method getTestMethod();
    
    Throwable getTestException();
    
    void markApplicationContextDirty(DirtiesContext.HierarchyMode hierarchyMode);
    
    void updateState(Object testInstance, Method testMethod, Throwable testException);
}

// Defines a strategy API for providing ApplicationContext instances
public interface ContextLoader {
    
    String[] processLocations(Class<?> clazz, String... locations);
    
    ApplicationContext loadContext(String... locations) throws Exception;
}

// Strategy for loading ApplicationContext from annotated classes
public class AnnotationConfigContextLoader extends AbstractGenericContextLoader {
    
    @Override
    protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context);
    
    @Override
    protected String getResourceSuffix();
}

Spring Boot Testing

Spring Boot Test Annotations

// Annotation for integration tests
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
public @interface SpringBootTest {
    
    @AliasFor("classes")
    Class<?>[] value() default {};
    
    Class<?>[] classes() default {};
    
    String[] properties() default {};
    
    WebEnvironment webEnvironment() default WebEnvironment.MOCK;
    
    // WebEnvironment enum
    enum WebEnvironment {
        MOCK,           // Load WebApplicationContext with mock web environment
        RANDOM_PORT,    // Load WebServerApplicationContext with random port
        DEFINED_PORT,   // Load WebServerApplicationContext with defined port
        NONE            // Load ApplicationContext without web environment
    }
}

// Annotation for testing web layer
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(WebMvcTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(WebMvcTypeExcludeFilter.class)
@AutoConfigureCache
@AutoConfigureWebMvc
@AutoConfigureMockMvc
@ImportAutoConfiguration
public @interface WebMvcTest {
    @AliasFor("controllers")
    Class<?>[] value() default {};
    
    Class<?>[] controllers() default {};
    
    boolean useDefaultFilters() default true;
    
    Filter[] includeFilters() default {};
    
    Filter[] excludeFilters() default {};
    
    String[] properties() default {};
}

// Annotation for testing JPA repositories
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(DataJpaTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
@OverrideAutoConfiguration(enabled = false)
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
@Transactional
@AutoConfigureCache
@AutoConfigureDataJpa
@AutoConfigureTestDatabase
@AutoConfigureTestEntityManager
@ImportAutoConfiguration
public @interface DataJpaTest {
    boolean showSql() default true;
    
    boolean useDefaultFilters() default true;
    
    Filter[] includeFilters() default {};
    
    Filter[] excludeFilters() default {};
    
    String[] properties() default {};
}

Mock and Spy Annotations

// Annotation to add mock objects to Spring ApplicationContext
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(MockBeans.class)
public @interface MockBean {
    
    String name() default "";
    
    Class<?>[] value() default {};
    
    Class<?>[] classes() default {};
    
    Class<?>[] extraInterfaces() default {};
    
    Answers answer() default Answers.RETURNS_DEFAULTS;
    
    boolean serializable() default false;
    
    MockReset reset() default MockReset.AFTER;
}

// Annotation to add spy objects to Spring ApplicationContext
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(SpyBeans.class)
public @interface SpyBean {
    
    String name() default "";
    
    Class<?>[] value() default {};
    
    Class<?>[] classes() default {};
    
    MockReset reset() default MockReset.AFTER;
}

// MockReset enum for controlling mock reset behavior
public enum MockReset {
    BEFORE, AFTER, NONE
}

MockMvc Testing

MockMvc Interface

// Main entry point for server-side Spring MVC testing
public final class MockMvc {
    
    // Perform a request
    public ResultActions perform(RequestBuilder requestBuilder) throws Exception;
    
    // Static factory methods
    public static MockMvcBuilder standaloneSetup(Object... controllers);
    public static DefaultMockMvcBuilder webAppContextSetup(WebApplicationContext context);
}

// Builder for creating MockMvc instances
public interface MockMvcBuilder {
    MockMvc build();
}

// Default MockMvc builder
public class DefaultMockMvcBuilder extends AbstractMockMvcBuilder<DefaultMockMvcBuilder> {
    
    public DefaultMockMvcBuilder addFilters(Filter... filters);
    public DefaultMockMvcBuilder addFilter(Filter filter, String... urlPatterns);
    public DefaultMockMvcBuilder dispatchOptions(boolean dispatchOptions);
    public DefaultMockMvcBuilder defaultRequest(RequestBuilder requestBuilder);
    public DefaultMockMvcBuilder alwaysExpect(ResultMatcher resultMatcher);
    public DefaultMockMvcBuilder alwaysDo(ResultHandler resultHandler);
}

// Interface for building requests
public interface RequestBuilder {
    MockHttpServletRequest buildRequest(ServletContext servletContext);
}

// Interface for actions that can be performed on the result
public interface ResultActions {
    ResultActions andExpect(ResultMatcher matcher) throws Exception;
    ResultActions andDo(ResultHandler handler) throws Exception;
    MvcResult andReturn();
}

// Interface for asserting the result
@FunctionalInterface
public interface ResultMatcher {
    void match(MvcResult result) throws Exception;
}

// Interface for handling the result
@FunctionalInterface
public interface ResultHandler {
    void handle(MvcResult result) throws Exception;
}

MockMvc Request Builders

// Utility class for building MockMvc requests
public class MockMvcRequestBuilders {
    
    // HTTP method builders
    public static MockHttpServletRequestBuilder get(String urlTemplate, Object... uriVars);
    public static MockHttpServletRequestBuilder get(URI uri);
    public static MockHttpServletRequestBuilder post(String urlTemplate, Object... uriVars);
    public static MockHttpServletRequestBuilder post(URI uri);
    public static MockHttpServletRequestBuilder put(String urlTemplate, Object... uriVars);
    public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... uriVars);
    public static MockHttpServletRequestBuilder patch(String urlTemplate, Object... uriVars);
    
    // File upload
    public static MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object... uriVars);
    public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... uriVars);
    
    // Async
    public static RequestBuilder asyncDispatch(MvcResult mvcResult);
}

// Builder for HTTP servlet requests
public class MockHttpServletRequestBuilder implements ConfigurableSmartRequestBuilder<MockHttpServletRequestBuilder> {
    
    // Request line
    public MockHttpServletRequestBuilder contextPath(String contextPath);
    public MockHttpServletRequestBuilder servletPath(String servletPath);
    public MockHttpServletRequestBuilder pathInfo(String pathInfo);
    
    // Parameters
    public MockHttpServletRequestBuilder param(String name, String... values);
    public MockHttpServletRequestBuilder params(MultiValueMap<String, String> params);
    public MockHttpServletRequestBuilder queryParam(String name, String... values);
    public MockHttpServletRequestBuilder queryParams(MultiValueMap<String, String> params);
    
    // Headers
    public MockHttpServletRequestBuilder header(String name, Object... values);
    public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders);
    public MockHttpServletRequestBuilder accept(MediaType... mediaTypes);
    public MockHttpServletRequestBuilder contentType(MediaType mediaType);
    
    // Body
    public MockHttpServletRequestBuilder content(String content);
    public MockHttpServletRequestBuilder content(byte[] content);
    public MockHttpServletRequestBuilder characterEncoding(String encoding);
    
    // Session and security
    public MockHttpServletRequestBuilder session(MockHttpSession session);
    public MockHttpServletRequestBuilder sessionAttr(String name, Object value);
    public MockHttpServletRequestBuilder flashAttr(String name, Object value);
    public MockHttpServletRequestBuilder cookie(Cookie... cookies);
    public MockHttpServletRequestBuilder locale(Locale locale);
    public MockHttpServletRequestBuilder principal(Principal principal);
    
    // Request attributes
    public MockHttpServletRequestBuilder requestAttr(String name, Object value);
    public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor);
}

MockMvc Result Matchers

// Utility class for asserting MockMvc results
public class MockMvcResultMatchers {
    
    // Status
    public static StatusResultMatchers status();
    
    // Headers  
    public static HeaderResultMatchers header();
    
    // Content
    public static ContentResultMatchers content();
    
    // JSON
    public static JsonPathResultMatchers jsonPath(String expression, Object... args);
    public static JsonPathResultMatchers jsonPath(String expression, Matcher<T> matcher);
    
    // View and Model
    public static ViewResultMatchers view();
    public static ModelResultMatchers model();
    
    // Flash attributes
    public static FlashAttributeResultMatchers flash();
    
    // Redirects
    public static ResultMatcher redirectedUrl(String expectedUrl);
    public static ResultMatcher redirectedUrlPattern(String redirectedUrlPattern);
    public static ResultMatcher forwardedUrl(String expectedUrl);
    
    // Cookies
    public static CookieResultMatchers cookie();
    
    // XPath
    public static XpathResultMatchers xpath(String expression, Object... args);
}

// Status result matchers
public class StatusResultMatchers {
    public ResultMatcher is(int status);
    public ResultMatcher isOk();
    public ResultMatcher isCreated();
    public ResultMatcher isAccepted();
    public ResultMatcher isNoContent();
    public ResultMatcher isBadRequest();
    public ResultMatcher isUnauthorized();
    public ResultMatcher isForbidden();
    public ResultMatcher isNotFound();
    public ResultMatcher isMethodNotAllowed();
    public ResultMatcher isConflict();
    public ResultMatcher isInternalServerError();
}

// Content result matchers
public class ContentResultMatchers {
    public ResultMatcher contentType(String contentType);
    public ResultMatcher contentType(MediaType contentType);
    public ResultMatcher contentTypeCompatibleWith(MediaType contentType);
    public ResultMatcher string(String expectedContent);
    public ResultMatcher string(Matcher<? super String> matcher);
    public ResultMatcher bytes(byte[] expectedContent);
    public ResultMatcher xml(String xmlContent);
    public ResultMatcher json(String jsonContent);
    public ResultMatcher json(String jsonContent, boolean strict);
}

WebTestClient (WebFlux Testing)

WebTestClient Interface

// Non-blocking, reactive client for testing web servers
public interface WebTestClient {
    
    // Request specification
    RequestHeadersUriSpec<?> get();
    RequestBodyUriSpec post();
    RequestBodyUriSpec put();
    RequestBodyUriSpec patch();
    RequestHeadersUriSpec<?> delete();
    RequestHeadersUriSpec<?> options();
    RequestHeadersUriSpec<?> head();
    RequestBodyUriSpec method(HttpMethod method);
    
    // Static factory methods
    static WebTestClient bindToServer();
    static WebTestClient bindToWebHandler(WebHandler webHandler);
    static WebTestClient bindToRouterFunction(RouterFunction<?> routerFunction);
    static WebTestClient bindToController(Object... controllers);
    static WebTestClient bindToApplicationContext(ApplicationContext applicationContext);
    
    // Response specification
    interface ResponseSpec {
        ResponseSpec expectStatus();
        ResponseSpec expectHeader();
        ResponseSpec expectCookie();
        ResponseSpec expectBody();
        <T> ResponseSpec expectBody(Class<T> bodyType);
        <T> ResponseSpec expectBodyList(Class<T> elementType);
        ResponseSpec expectCookies();
        
        // Terminal operations
        <T> EntityExchangeResult<T> returnResult(Class<T> bodyType);
        <T> FluxExchangeResult<T> returnResult(ParameterizedTypeReference<T> bodyTypeReference);
        EntityExchangeResult<Void> returnResult(Void.class);
    }
}

Transaction Testing

Transaction Annotations

// Indicates that transaction should be rolled back after test method
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Rollback {
    boolean value() default true;
}

// Indicates that transaction should be committed after test method
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Commit {
}

// Defines SQL scripts to execute before/after test methods
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(SqlGroup.class)
public @interface Sql {
    
    @AliasFor("scripts")
    String[] value() default {};
    
    @AliasFor("value")  
    String[] scripts() default {};
    
    ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD;
    
    SqlConfig config() default @SqlConfig;
    
    // Execution phase enum
    enum ExecutionPhase {
        BEFORE_TEST_METHOD, AFTER_TEST_METHOD
    }
}

// Configuration for @Sql
@Target({})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SqlConfig {
    
    String dataSource() default "";
    
    String transactionManager() default "";
    
    TransactionMode transactionMode() default TransactionMode.DEFAULT;
    
    String encoding() default "";
    
    String separator() default ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
    
    String commentPrefix() default ScriptUtils.DEFAULT_COMMENT_PREFIX;
    
    String blockCommentStartDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
    
    String blockCommentEndDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
    
    ErrorMode errorMode() default ErrorMode.DEFAULT;
    
    // Enums
    enum TransactionMode { DEFAULT, ISOLATED, INFERRED }
    enum ErrorMode { DEFAULT, FAIL_ON_ERROR, CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS }
}

Practical Usage Examples

Basic Integration Testing

@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
@ActiveProfiles("test")
class UserServiceIntegrationTest {
    
    @Autowired
    private UserService userService;
    
    @MockBean
    private EmailService emailService;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    @Transactional
    @Rollback
    void shouldCreateUserSuccessfully() {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
            .username("testuser")
            .email("test@example.com")
            .firstName("Test")
            .lastName("User")
            .build();
        
        // When
        User createdUser = userService.createUser(request);
        
        // Then
        assertThat(createdUser).isNotNull();
        assertThat(createdUser.getId()).isNotNull();
        assertThat(createdUser.getUsername()).isEqualTo("testuser");
        assertThat(createdUser.getEmail()).isEqualTo("test@example.com");
        
        // Verify email service was called
        verify(emailService).sendWelcomeEmail(createdUser);
        
        // Verify user was persisted
        Optional<User> savedUser = userRepository.findById(createdUser.getId());
        assertThat(savedUser).isPresent();
        assertThat(savedUser.get().getUsername()).isEqualTo("testuser");
    }
    
    @Test
    @Transactional
    void shouldThrowExceptionWhenUserAlreadyExists() {
        // Given - create user first
        User existingUser = User.builder()
            .username("duplicate")
            .email("duplicate@example.com")
            .build();
        userRepository.save(existingUser);
        
        CreateUserRequest request = CreateUserRequest.builder()
            .username("duplicate")
            .email("different@example.com")
            .build();
        
        // When & Then
        assertThatThrownBy(() -> userService.createUser(request))
            .isInstanceOf(UserAlreadyExistsException.class)
            .hasMessageContaining("User already exists");
        
        verifyNoInteractions(emailService);
    }
}

Web Layer Testing with MockMvc

@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Test
    void shouldReturnUserWhenValidId() throws Exception {
        // Given
        Long userId = 1L;
        User user = User.builder()
            .id(userId)
            .username("testuser")
            .email("test@example.com")
            .build();
        
        when(userService.findById(userId)).thenReturn(user);
        
        // When & Then
        mockMvc.perform(get("/api/users/{id}", userId)
                .contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(jsonPath("$.id").value(userId))
            .andExpect(jsonPath("$.username").value("testuser"))
            .andExpect(jsonPath("$.email").value("test@example.com"))
            .andDo(print());
        
        verify(userService).findById(userId);
    }
    
    @Test
    void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
        // Given
        Long userId = 999L;
        when(userService.findById(userId))
            .thenThrow(new UserNotFoundException("User not found"));
        
        // When & Then
        mockMvc.perform(get("/api/users/{id}", userId))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
            .andExpect(jsonPath("$.message").value("User not found"))
            .andExpect(jsonPath("$.code").value("USER_NOT_FOUND"));
    }
    
    @Test
    void shouldCreateUserWhenValidRequest() throws Exception {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
            .username("newuser")
            .email("new@example.com")
            .firstName("New")
            .lastName("User")
            .build();
        
        User createdUser = User.builder()
            .id(1L)
            .username("newuser")
            .email("new@example.com")
            .firstName("New")
            .lastName("User")
            .build();
        
        when(userService.createUser(any(CreateUserRequest.class)))
            .thenReturn(createdUser);
        
        // When & Then
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isCreated())
            .andExpect(header().exists("Location"))
            .andExpect(jsonPath("$.id").value(1))
            .andExpected(jsonPath("$.username").value("newuser"));
        
        verify(userService).createUser(any(CreateUserRequest.class));
    }
    
    @Test
    void shouldReturnBadRequestWhenValidationFails() throws Exception {
        // Given
        CreateUserRequest invalidRequest = CreateUserRequest.builder()
            .username("") // Invalid - empty username
            .email("invalid-email") // Invalid email format
            .build();
        
        // When & Then
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidRequest)))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.validationErrors").exists())
            .andExpect(jsonPath("$.validationErrors.username").exists())
            .andExpect(jsonPath("$.validationErrors.email").exists());
        
        verifyNoInteractions(userService);
    }
    
    @Test
    void shouldUpdateUserWhenValidRequest() throws Exception {
        // Given
        Long userId = 1L;
        UpdateUserRequest request = UpdateUserRequest.builder()
            .username("updateduser")
            .email("updated@example.com")
            .build();
        
        User updatedUser = User.builder()
            .id(userId)
            .username("updateduser")
            .email("updated@example.com")
            .build();
        
        when(userService.updateUser(eq(userId), any(UpdateUserRequest.class)))
            .thenReturn(updatedUser);
        
        // When & Then
        mockMvc.perform(put("/api/users/{id}", userId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.username").value("updateduser"))
            .andExpect(jsonPath("$.email").value("updated@example.com"));
    }
    
    @Test
    void shouldReturnUsersWithPagination() throws Exception {
        // Given
        List<User> users = Arrays.asList(
            User.builder().id(1L).username("user1").build(),
            User.builder().id(2L).username("user2").build()
        );
        
        Page<User> userPage = new PageImpl<>(users, PageRequest.of(0, 10), 2);
        when(userService.findAll(any(Pageable.class))).thenReturn(userPage);
        
        // When & Then
        mockMvc.perform(get("/api/users")
                .param("page", "0")
                .param("size", "10"))
            .andExpected(status().isOk())
            .andExpect(jsonPath("$.content").isArray())
            .andExpect(jsonPath("$.content.length()").value(2))
            .andExpect(jsonPath("$.content[0].username").value("user1"))
            .andExpect(jsonPath("$.totalElements").value(2))
            .andExpect(header().string("X-Total-Count", "2"));
    }
}

Repository Layer Testing

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void shouldFindUserByUsername() {
        // Given
        User user = User.builder()
            .username("testuser")
            .email("test@example.com")
            .firstName("Test")
            .lastName("User")
            .build();
        
        entityManager.persistAndFlush(user);
        
        // When
        Optional<User> foundUser = userRepository.findByUsername("testuser");
        
        // Then
        assertThat(foundUser).isPresent();
        assertThat(foundUser.get().getUsername()).isEqualTo("testuser");
        assertThat(foundUser.get().getEmail()).isEqualTo("test@example.com");
    }
    
    @Test
    void shouldReturnEmptyWhenUserNotFound() {
        // When
        Optional<User> foundUser = userRepository.findByUsername("nonexistent");
        
        // Then
        assertThat(foundUser).isEmpty();
    }
    
    @Test
    void shouldFindUsersByEmailDomain() {
        // Given
        User user1 = User.builder()
            .username("user1")
            .email("user1@company.com")
            .build();
        
        User user2 = User.builder()
            .username("user2")
            .email("user2@company.com")
            .build();
        
        User user3 = User.builder()
            .username("user3")
            .email("user3@gmail.com")
            .build();
        
        entityManager.persist(user1);
        entityManager.persist(user2);
        entityManager.persist(user3);
        entityManager.flush();
        
        // When
        List<User> companyUsers = userRepository.findByEmailContaining("@company.com");
        
        // Then
        assertThat(companyUsers).hasSize(2);
        assertThat(companyUsers).extracting(User::getUsername)
            .containsExactlyInAnyOrder("user1", "user2");
    }
    
    @Test
    @Sql("/test-data/users.sql")
    void shouldCountActiveUsers() {
        // When
        long activeCount = userRepository.countByActiveTrue();
        
        // Then
        assertThat(activeCount).isEqualTo(3); // Based on test data
    }
    
    @Test
    void shouldDeleteUsersByCreatedDateBefore() {
        // Given
        LocalDateTime cutoffDate = LocalDateTime.now().minusDays(30);
        
        User oldUser = User.builder()
            .username("olduser")
            .email("old@example.com")
            .createdDate(cutoffDate.minusDays(1))
            .build();
        
        User newUser = User.builder()
            .username("newuser")
            .email("new@example.com")
            .createdDate(cutoffDate.plusDays(1))
            .build();
        
        entityManager.persist(oldUser);
        entityManager.persist(newUser);
        entityManager.flush();
        
        // When
        int deletedCount = userRepository.deleteByCreatedDateBefore(cutoffDate);
        
        // Then
        assertThat(deletedCount).isEqualTo(1);
        
        List<User> remainingUsers = userRepository.findAll();
        assertThat(remainingUsers).hasSize(1);
        assertThat(remainingUsers.get(0).getUsername()).isEqualTo("newuser");
    }
}

WebFlux Testing with WebTestClient

@WebFluxTest(ReactiveUserController.class)
class ReactiveUserControllerTest {
    
    @Autowired
    private WebTestClient webTestClient;
    
    @MockBean
    private ReactiveUserService userService;
    
    @Test
    void shouldReturnUserWhenFound() {
        // Given
        Long userId = 1L;
        User user = User.builder()
            .id(userId)
            .username("testuser")
            .email("test@example.com")
            .build();
        
        when(userService.findById(userId)).thenReturn(Mono.just(user));
        
        // When & Then
        webTestClient.get()
            .uri("/api/users/{id}", userId)
            .exchange()
            .expectStatus().isOk()
            .expectHeader().contentType(MediaType.APPLICATION_JSON)
            .expectBody(User.class)
            .value(returnedUser -> {
                assertThat(returnedUser.getId()).isEqualTo(userId);
                assertThat(returnedUser.getUsername()).isEqualTo("testuser");
            });
    }
    
    @Test
    void shouldReturnNotFoundWhenUserDoesNotExist() {
        // Given
        Long userId = 999L;
        when(userService.findById(userId)).thenReturn(Mono.empty());
        
        // When & Then
        webTestClient.get()
            .uri("/api/users/{id}", userId)
            .exchange()
            .expectStatus().isNotFound();
    }
    
    @Test
    void shouldCreateUserSuccessfully() {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
            .username("newuser")
            .email("new@example.com")
            .build();
        
        User createdUser = User.builder()
            .id(1L)
            .username("newuser")
            .email("new@example.com")
            .build();
        
        when(userService.createUser(any(CreateUserRequest.class)))
            .thenReturn(Mono.just(createdUser));
        
        // When & Then
        webTestClient.post()
            .uri("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(request)
            .exchange()
            .expectStatus().isCreated()
            .expectHeader().exists("Location")
            .expectBody(User.class)
            .value(user -> {
                assertThat(user.getId()).isEqualTo(1L);
                assertThat(user.getUsername()).isEqualTo("newuser");
            });
    }
    
    @Test
    void shouldStreamUsers() {
        // Given
        List<User> users = Arrays.asList(
            User.builder().id(1L).username("user1").build(),
            User.builder().id(2L).username("user2").build(),
            User.builder().id(3L).username("user3").build()
        );
        
        when(userService.findAll()).thenReturn(Flux.fromIterable(users));
        
        // When & Then
        webTestClient.get()
            .uri("/api/users/stream")
            .accept(MediaType.TEXT_EVENT_STREAM)
            .exchange()
            .expectStatus().isOk()
            .expectHeader().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)
            .expectBodyList(User.class)
            .hasSize(3)
            .contains(users.toArray(new User[0]));
    }
    
    @Test
    void shouldHandleValidationErrors() {
        // Given
        CreateUserRequest invalidRequest = CreateUserRequest.builder()
            .username("") // Invalid
            .build();
        
        // When & Then
        webTestClient.post()
            .uri("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .bodyValue(invalidRequest)
            .exchange()
            .expectStatus().isBadRequest()
            .expectBody()
            .jsonPath("$.validationErrors").exists()
            .jsonPath("$.validationErrors.username").exists();
    }
}

Custom Test Configuration

// Test configuration class
@TestConfiguration
public class TestConfig {
    
    @Bean
    @Primary
    public Clock testClock() {
        return Clock.fixed(Instant.parse("2023-01-01T00:00:00Z"), ZoneOffset.UTC);
    }
    
    @Bean
    public ObjectMapper testObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }
    
    @Bean
    @Primary
    public PasswordEncoder mockPasswordEncoder() {
        return Mockito.mock(PasswordEncoder.class);
    }
}

// Base test class with common configuration
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
@Import(TestConfig.class)
@Transactional
public abstract class BaseIntegrationTest {
    
    @Autowired
    protected TestEntityManager entityManager;
    
    @MockBean
    protected EmailService emailService;
    
    @BeforeEach
    void setUp() {
        // Common setup
        Mockito.reset(emailService);
    }
    
    @AfterEach
    void tearDown() {
        // Common cleanup
    }
    
    protected User createTestUser(String username) {
        User user = User.builder()
            .username(username)
            .email(username + "@example.com")
            .build();
        return entityManager.persistAndFlush(user);
    }
}

// Test slices with custom configuration
@WebMvcTest
@Import({SecurityTestConfig.class, TestConfig.class})
class SecureControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Test
    @WithMockUser(roles = "ADMIN")
    void shouldAllowAdminAccess() throws Exception {
        mockMvc.perform(get("/admin/users"))
            .andExpected(status().isOk());
    }
    
    @Test
    @WithMockUser(roles = "USER")
    void shouldDenyUserAccess() throws Exception {
        mockMvc.perform(get("/admin/users"))
            .andExpected(status().isForbidden());
    }
}

// Parameterized tests
class UserValidationTest {
    
    @ParameterizedTest
    @ValueSource(strings = {"", " ", "ab", "a".repeat(51)})
    void shouldRejectInvalidUsernames(String username) {
        CreateUserRequest request = CreateUserRequest.builder()
            .username(username)
            .email("test@example.com")
            .build();
        
        Set<ConstraintViolation<CreateUserRequest>> violations = validator.validate(request);
        
        assertThat(violations).isNotEmpty();
        assertThat(violations).anyMatch(v -> v.getPropertyPath().toString().equals("username"));
    }
    
    @ParameterizedTest
    @CsvSource({
        "test@example.com, true",
        "invalid-email, false",
        "@example.com, false",
        "test@, false",
        "test.email@domain.co.uk, true"
    })
    void shouldValidateEmailFormats(String email, boolean shouldBeValid) {
        CreateUserRequest request = CreateUserRequest.builder()
            .username("testuser")
            .email(email)
            .build();
        
        Set<ConstraintViolation<CreateUserRequest>> violations = validator.validate(request);
        boolean hasEmailViolation = violations.stream()
            .anyMatch(v -> v.getPropertyPath().toString().equals("email"));
        
        assertThat(hasEmailViolation).isEqualTo(!shouldBeValid);
    }
}

Performance and Load Testing

// Performance test with JMH (requires jmh dependency)
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 2)
@Measurement(iterations = 5)
@State(Scope.Benchmark)
public class UserServicePerformanceTest {
    
    private UserService userService;
    private CreateUserRequest request;
    
    @Setup
    public void setup() {
        // Initialize Spring context
        ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
        userService = context.getBean(UserService.class);
        
        request = CreateUserRequest.builder()
            .username("testuser")
            .email("test@example.com")
            .build();
    }
    
    @Benchmark
    public User createUser() {
        return userService.createUser(request);
    }
}

// Concurrent testing
@SpringBootTest
class ConcurrentUserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    void shouldHandleConcurrentUserCreation() throws InterruptedException {
        int numberOfThreads = 10;
        int usersPerThread = 100;
        CountDownLatch latch = new CountDownLatch(numberOfThreads);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger errorCount = new AtomicInteger(0);
        
        ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
        
        for (int i = 0; i < numberOfThreads; i++) {
            final int threadId = i;
            executor.submit(() -> {
                try {
                    for (int j = 0; j < usersPerThread; j++) {
                        CreateUserRequest request = CreateUserRequest.builder()
                            .username("user_" + threadId + "_" + j)
                            .email("user_" + threadId + "_" + j + "@example.com")
                            .build();
                        
                        try {
                            userService.createUser(request);
                            successCount.incrementAndGet();
                        } catch (Exception e) {
                            errorCount.incrementAndGet();
                        }
                    }
                } finally {
                    latch.countDown();
                }
            });
        }
        
        latch.await(30, TimeUnit.SECONDS);
        executor.shutdown();
        
        int expectedTotal = numberOfThreads * usersPerThread;
        assertThat(successCount.get() + errorCount.get()).isEqualTo(expectedTotal);
        assertThat(errorCount.get()).isLessThan(expectedTotal * 0.1); // Less than 10% errors
    }
}

Spring Test provides comprehensive testing capabilities that enable you to test all layers of your Spring application effectively, from unit tests with mocks to full integration tests with real databases and web servers.

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework--spring

docs

aop.md

core-container.md

data-access.md

index.md

integration.md

messaging.md

reactive-web.md

testing.md

web-framework.md

tile.json