0
# Testing Support
1
2
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.
3
4
## Maven Dependencies
5
6
```xml
7
<!-- Spring Test -->
8
<dependency>
9
<groupId>org.springframework</groupId>
10
<artifactId>spring-test</artifactId>
11
<version>5.3.39</version>
12
<scope>test</scope>
13
</dependency>
14
15
<!-- JUnit 5 -->
16
<dependency>
17
<groupId>org.junit.jupiter</groupId>
18
<artifactId>junit-jupiter</artifactId>
19
<version>5.8.2</version>
20
<scope>test</scope>
21
</dependency>
22
23
<!-- Mockito -->
24
<dependency>
25
<groupId>org.mockito</groupId>
26
<artifactId>mockito-core</artifactId>
27
<version>4.6.1</version>
28
<scope>test</scope>
29
</dependency>
30
31
<!-- AssertJ -->
32
<dependency>
33
<groupId>org.assertj</groupId>
34
<artifactId>assertj-core</artifactId>
35
<version>3.24.2</version>
36
<scope>test</scope>
37
</dependency>
38
39
<!-- Spring Boot Test (if using Spring Boot) -->
40
<dependency>
41
<groupId>org.springframework.boot</groupId>
42
<artifactId>spring-boot-starter-test</artifactId>
43
<scope>test</scope>
44
</dependency>
45
```
46
47
## Core Imports
48
49
```java { .api }
50
// TestContext Framework
51
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
52
import org.springframework.test.context.junit.jupiter.SpringExtension;
53
import org.springframework.test.context.ContextConfiguration;
54
import org.springframework.test.context.TestPropertySource;
55
import org.springframework.test.context.ActiveProfiles;
56
import org.springframework.test.annotation.DirtiesContext;
57
58
// Spring Boot Testing
59
import org.springframework.boot.test.context.SpringBootTest;
60
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
61
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
62
import org.springframework.boot.test.mock.mockito.MockBean;
63
import org.springframework.boot.test.mock.mockito.SpyBean;
64
65
// MockMvc
66
import org.springframework.test.web.servlet.MockMvc;
67
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
68
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
69
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
70
71
// WebTestClient (WebFlux)
72
import org.springframework.test.web.reactive.server.WebTestClient;
73
74
// Transaction Testing
75
import org.springframework.test.annotation.Rollback;
76
import org.springframework.test.annotation.Commit;
77
import org.springframework.transaction.annotation.Transactional;
78
79
// JUnit 5
80
import org.junit.jupiter.api.Test;
81
import org.junit.jupiter.api.BeforeEach;
82
import org.junit.jupiter.api.AfterEach;
83
import org.junit.jupiter.api.extension.ExtendWith;
84
85
// Assertions
86
import static org.assertj.core.api.Assertions.*;
87
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
88
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
89
```
90
91
## TestContext Framework
92
93
### Core Testing Annotations
94
95
```java { .api }
96
// Combination of @ExtendWith(SpringExtension.class) and @ContextConfiguration
97
@Target(ElementType.TYPE)
98
@Retention(RetentionPolicy.RUNTIME)
99
@Documented
100
@ExtendWith(SpringExtension.class)
101
@ContextConfiguration
102
public @interface SpringJUnitConfig {
103
@AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
104
Class<?>[] value() default {};
105
106
@AliasFor(annotation = ContextConfiguration.class, attribute = "classes")
107
Class<?>[] classes() default {};
108
109
@AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
110
String[] locations() default {};
111
}
112
113
// Defines class-level metadata for configuring TestContext
114
@Target(ElementType.TYPE)
115
@Retention(RetentionPolicy.RUNTIME)
116
@Documented
117
@Inherited
118
public @interface ContextConfiguration {
119
@AliasFor("locations")
120
String[] value() default {};
121
122
String[] locations() default {};
123
124
Class<?>[] classes() default {};
125
126
Class<? extends ApplicationContextInitializer<?>>[] initializers() default {};
127
128
boolean inheritLocations() default true;
129
130
boolean inheritInitializers() default true;
131
132
String name() default "";
133
}
134
135
// Indicates which active bean definition profiles should be used
136
@Target({ElementType.TYPE, ElementType.METHOD})
137
@Retention(RetentionPolicy.RUNTIME)
138
@Documented
139
@Inherited
140
public @interface ActiveProfiles {
141
@AliasFor("profiles")
142
String[] value() default {};
143
144
String[] profiles() default {};
145
146
boolean inheritProfiles() default true;
147
148
Class<? extends ActiveProfilesResolver> resolver() default ActiveProfilesResolver.class;
149
}
150
151
// Configures test property sources
152
@Target(ElementType.TYPE)
153
@Retention(RetentionPolicy.RUNTIME)
154
@Documented
155
@Inherited
156
@Repeatable(TestPropertySources.class)
157
public @interface TestPropertySource {
158
@AliasFor("locations")
159
String[] value() default {};
160
161
String[] locations() default {};
162
163
boolean inheritLocations() default true;
164
165
String[] properties() default {};
166
167
boolean inheritProperties() default true;
168
}
169
170
// Indicates that ApplicationContext should be closed and removed from cache
171
@Target({ElementType.TYPE, ElementType.METHOD})
172
@Retention(RetentionPolicy.RUNTIME)
173
@Documented
174
@Inherited
175
public @interface DirtiesContext {
176
ScopeMode value() default ScopeMode.DEFAULT_SCOPE;
177
178
ClassMode classMode() default ClassMode.AFTER_CLASS;
179
180
MethodMode methodMode() default MethodMode.AFTER_METHOD;
181
182
Class<? extends HierarchyMode> hierarchyMode() default HierarchyMode.EXHAUSTIVE;
183
184
enum ClassMode {
185
BEFORE_CLASS, BEFORE_EACH_TEST_METHOD, AFTER_EACH_TEST_METHOD, AFTER_CLASS
186
}
187
188
enum MethodMode {
189
BEFORE_METHOD, AFTER_METHOD
190
}
191
192
enum HierarchyMode {
193
CURRENT_LEVEL, EXHAUSTIVE
194
}
195
}
196
```
197
198
### TestContext Interface
199
200
```java { .api }
201
// Encapsulates the context for running a test
202
public interface TestContext extends AttributeAccessor, Serializable {
203
204
ApplicationContext getApplicationContext();
205
206
Class<?> getTestClass();
207
208
Object getTestInstance();
209
210
Method getTestMethod();
211
212
Throwable getTestException();
213
214
void markApplicationContextDirty(DirtiesContext.HierarchyMode hierarchyMode);
215
216
void updateState(Object testInstance, Method testMethod, Throwable testException);
217
}
218
219
// Defines a strategy API for providing ApplicationContext instances
220
public interface ContextLoader {
221
222
String[] processLocations(Class<?> clazz, String... locations);
223
224
ApplicationContext loadContext(String... locations) throws Exception;
225
}
226
227
// Strategy for loading ApplicationContext from annotated classes
228
public class AnnotationConfigContextLoader extends AbstractGenericContextLoader {
229
230
@Override
231
protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context);
232
233
@Override
234
protected String getResourceSuffix();
235
}
236
```
237
238
## Spring Boot Testing
239
240
### Spring Boot Test Annotations
241
242
```java { .api }
243
// Annotation for integration tests
244
@Target(ElementType.TYPE)
245
@Retention(RetentionPolicy.RUNTIME)
246
@Documented
247
@Inherited
248
@BootstrapWith(SpringBootTestContextBootstrapper.class)
249
@ExtendWith(SpringExtension.class)
250
public @interface SpringBootTest {
251
252
@AliasFor("classes")
253
Class<?>[] value() default {};
254
255
Class<?>[] classes() default {};
256
257
String[] properties() default {};
258
259
WebEnvironment webEnvironment() default WebEnvironment.MOCK;
260
261
// WebEnvironment enum
262
enum WebEnvironment {
263
MOCK, // Load WebApplicationContext with mock web environment
264
RANDOM_PORT, // Load WebServerApplicationContext with random port
265
DEFINED_PORT, // Load WebServerApplicationContext with defined port
266
NONE // Load ApplicationContext without web environment
267
}
268
}
269
270
// Annotation for testing web layer
271
@Target(ElementType.TYPE)
272
@Retention(RetentionPolicy.RUNTIME)
273
@Documented
274
@Inherited
275
@BootstrapWith(WebMvcTestContextBootstrapper.class)
276
@ExtendWith(SpringExtension.class)
277
@OverrideAutoConfiguration(enabled = false)
278
@TypeExcludeFilters(WebMvcTypeExcludeFilter.class)
279
@AutoConfigureCache
280
@AutoConfigureWebMvc
281
@AutoConfigureMockMvc
282
@ImportAutoConfiguration
283
public @interface WebMvcTest {
284
@AliasFor("controllers")
285
Class<?>[] value() default {};
286
287
Class<?>[] controllers() default {};
288
289
boolean useDefaultFilters() default true;
290
291
Filter[] includeFilters() default {};
292
293
Filter[] excludeFilters() default {};
294
295
String[] properties() default {};
296
}
297
298
// Annotation for testing JPA repositories
299
@Target(ElementType.TYPE)
300
@Retention(RetentionPolicy.RUNTIME)
301
@Documented
302
@Inherited
303
@BootstrapWith(DataJpaTestContextBootstrapper.class)
304
@ExtendWith(SpringExtension.class)
305
@OverrideAutoConfiguration(enabled = false)
306
@TypeExcludeFilters(DataJpaTypeExcludeFilter.class)
307
@Transactional
308
@AutoConfigureCache
309
@AutoConfigureDataJpa
310
@AutoConfigureTestDatabase
311
@AutoConfigureTestEntityManager
312
@ImportAutoConfiguration
313
public @interface DataJpaTest {
314
boolean showSql() default true;
315
316
boolean useDefaultFilters() default true;
317
318
Filter[] includeFilters() default {};
319
320
Filter[] excludeFilters() default {};
321
322
String[] properties() default {};
323
}
324
```
325
326
### Mock and Spy Annotations
327
328
```java { .api }
329
// Annotation to add mock objects to Spring ApplicationContext
330
@Target({ElementType.TYPE, ElementType.FIELD})
331
@Retention(RetentionPolicy.RUNTIME)
332
@Documented
333
@Repeatable(MockBeans.class)
334
public @interface MockBean {
335
336
String name() default "";
337
338
Class<?>[] value() default {};
339
340
Class<?>[] classes() default {};
341
342
Class<?>[] extraInterfaces() default {};
343
344
Answers answer() default Answers.RETURNS_DEFAULTS;
345
346
boolean serializable() default false;
347
348
MockReset reset() default MockReset.AFTER;
349
}
350
351
// Annotation to add spy objects to Spring ApplicationContext
352
@Target({ElementType.TYPE, ElementType.FIELD})
353
@Retention(RetentionPolicy.RUNTIME)
354
@Documented
355
@Repeatable(SpyBeans.class)
356
public @interface SpyBean {
357
358
String name() default "";
359
360
Class<?>[] value() default {};
361
362
Class<?>[] classes() default {};
363
364
MockReset reset() default MockReset.AFTER;
365
}
366
367
// MockReset enum for controlling mock reset behavior
368
public enum MockReset {
369
BEFORE, AFTER, NONE
370
}
371
```
372
373
## MockMvc Testing
374
375
### MockMvc Interface
376
377
```java { .api }
378
// Main entry point for server-side Spring MVC testing
379
public final class MockMvc {
380
381
// Perform a request
382
public ResultActions perform(RequestBuilder requestBuilder) throws Exception;
383
384
// Static factory methods
385
public static MockMvcBuilder standaloneSetup(Object... controllers);
386
public static DefaultMockMvcBuilder webAppContextSetup(WebApplicationContext context);
387
}
388
389
// Builder for creating MockMvc instances
390
public interface MockMvcBuilder {
391
MockMvc build();
392
}
393
394
// Default MockMvc builder
395
public class DefaultMockMvcBuilder extends AbstractMockMvcBuilder<DefaultMockMvcBuilder> {
396
397
public DefaultMockMvcBuilder addFilters(Filter... filters);
398
public DefaultMockMvcBuilder addFilter(Filter filter, String... urlPatterns);
399
public DefaultMockMvcBuilder dispatchOptions(boolean dispatchOptions);
400
public DefaultMockMvcBuilder defaultRequest(RequestBuilder requestBuilder);
401
public DefaultMockMvcBuilder alwaysExpect(ResultMatcher resultMatcher);
402
public DefaultMockMvcBuilder alwaysDo(ResultHandler resultHandler);
403
}
404
405
// Interface for building requests
406
public interface RequestBuilder {
407
MockHttpServletRequest buildRequest(ServletContext servletContext);
408
}
409
410
// Interface for actions that can be performed on the result
411
public interface ResultActions {
412
ResultActions andExpect(ResultMatcher matcher) throws Exception;
413
ResultActions andDo(ResultHandler handler) throws Exception;
414
MvcResult andReturn();
415
}
416
417
// Interface for asserting the result
418
@FunctionalInterface
419
public interface ResultMatcher {
420
void match(MvcResult result) throws Exception;
421
}
422
423
// Interface for handling the result
424
@FunctionalInterface
425
public interface ResultHandler {
426
void handle(MvcResult result) throws Exception;
427
}
428
```
429
430
### MockMvc Request Builders
431
432
```java { .api }
433
// Utility class for building MockMvc requests
434
public class MockMvcRequestBuilders {
435
436
// HTTP method builders
437
public static MockHttpServletRequestBuilder get(String urlTemplate, Object... uriVars);
438
public static MockHttpServletRequestBuilder get(URI uri);
439
public static MockHttpServletRequestBuilder post(String urlTemplate, Object... uriVars);
440
public static MockHttpServletRequestBuilder post(URI uri);
441
public static MockHttpServletRequestBuilder put(String urlTemplate, Object... uriVars);
442
public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... uriVars);
443
public static MockHttpServletRequestBuilder patch(String urlTemplate, Object... uriVars);
444
445
// File upload
446
public static MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object... uriVars);
447
public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... uriVars);
448
449
// Async
450
public static RequestBuilder asyncDispatch(MvcResult mvcResult);
451
}
452
453
// Builder for HTTP servlet requests
454
public class MockHttpServletRequestBuilder implements ConfigurableSmartRequestBuilder<MockHttpServletRequestBuilder> {
455
456
// Request line
457
public MockHttpServletRequestBuilder contextPath(String contextPath);
458
public MockHttpServletRequestBuilder servletPath(String servletPath);
459
public MockHttpServletRequestBuilder pathInfo(String pathInfo);
460
461
// Parameters
462
public MockHttpServletRequestBuilder param(String name, String... values);
463
public MockHttpServletRequestBuilder params(MultiValueMap<String, String> params);
464
public MockHttpServletRequestBuilder queryParam(String name, String... values);
465
public MockHttpServletRequestBuilder queryParams(MultiValueMap<String, String> params);
466
467
// Headers
468
public MockHttpServletRequestBuilder header(String name, Object... values);
469
public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders);
470
public MockHttpServletRequestBuilder accept(MediaType... mediaTypes);
471
public MockHttpServletRequestBuilder contentType(MediaType mediaType);
472
473
// Body
474
public MockHttpServletRequestBuilder content(String content);
475
public MockHttpServletRequestBuilder content(byte[] content);
476
public MockHttpServletRequestBuilder characterEncoding(String encoding);
477
478
// Session and security
479
public MockHttpServletRequestBuilder session(MockHttpSession session);
480
public MockHttpServletRequestBuilder sessionAttr(String name, Object value);
481
public MockHttpServletRequestBuilder flashAttr(String name, Object value);
482
public MockHttpServletRequestBuilder cookie(Cookie... cookies);
483
public MockHttpServletRequestBuilder locale(Locale locale);
484
public MockHttpServletRequestBuilder principal(Principal principal);
485
486
// Request attributes
487
public MockHttpServletRequestBuilder requestAttr(String name, Object value);
488
public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor);
489
}
490
```
491
492
### MockMvc Result Matchers
493
494
```java { .api }
495
// Utility class for asserting MockMvc results
496
public class MockMvcResultMatchers {
497
498
// Status
499
public static StatusResultMatchers status();
500
501
// Headers
502
public static HeaderResultMatchers header();
503
504
// Content
505
public static ContentResultMatchers content();
506
507
// JSON
508
public static JsonPathResultMatchers jsonPath(String expression, Object... args);
509
public static JsonPathResultMatchers jsonPath(String expression, Matcher<T> matcher);
510
511
// View and Model
512
public static ViewResultMatchers view();
513
public static ModelResultMatchers model();
514
515
// Flash attributes
516
public static FlashAttributeResultMatchers flash();
517
518
// Redirects
519
public static ResultMatcher redirectedUrl(String expectedUrl);
520
public static ResultMatcher redirectedUrlPattern(String redirectedUrlPattern);
521
public static ResultMatcher forwardedUrl(String expectedUrl);
522
523
// Cookies
524
public static CookieResultMatchers cookie();
525
526
// XPath
527
public static XpathResultMatchers xpath(String expression, Object... args);
528
}
529
530
// Status result matchers
531
public class StatusResultMatchers {
532
public ResultMatcher is(int status);
533
public ResultMatcher isOk();
534
public ResultMatcher isCreated();
535
public ResultMatcher isAccepted();
536
public ResultMatcher isNoContent();
537
public ResultMatcher isBadRequest();
538
public ResultMatcher isUnauthorized();
539
public ResultMatcher isForbidden();
540
public ResultMatcher isNotFound();
541
public ResultMatcher isMethodNotAllowed();
542
public ResultMatcher isConflict();
543
public ResultMatcher isInternalServerError();
544
}
545
546
// Content result matchers
547
public class ContentResultMatchers {
548
public ResultMatcher contentType(String contentType);
549
public ResultMatcher contentType(MediaType contentType);
550
public ResultMatcher contentTypeCompatibleWith(MediaType contentType);
551
public ResultMatcher string(String expectedContent);
552
public ResultMatcher string(Matcher<? super String> matcher);
553
public ResultMatcher bytes(byte[] expectedContent);
554
public ResultMatcher xml(String xmlContent);
555
public ResultMatcher json(String jsonContent);
556
public ResultMatcher json(String jsonContent, boolean strict);
557
}
558
```
559
560
## WebTestClient (WebFlux Testing)
561
562
### WebTestClient Interface
563
564
```java { .api }
565
// Non-blocking, reactive client for testing web servers
566
public interface WebTestClient {
567
568
// Request specification
569
RequestHeadersUriSpec<?> get();
570
RequestBodyUriSpec post();
571
RequestBodyUriSpec put();
572
RequestBodyUriSpec patch();
573
RequestHeadersUriSpec<?> delete();
574
RequestHeadersUriSpec<?> options();
575
RequestHeadersUriSpec<?> head();
576
RequestBodyUriSpec method(HttpMethod method);
577
578
// Static factory methods
579
static WebTestClient bindToServer();
580
static WebTestClient bindToWebHandler(WebHandler webHandler);
581
static WebTestClient bindToRouterFunction(RouterFunction<?> routerFunction);
582
static WebTestClient bindToController(Object... controllers);
583
static WebTestClient bindToApplicationContext(ApplicationContext applicationContext);
584
585
// Response specification
586
interface ResponseSpec {
587
ResponseSpec expectStatus();
588
ResponseSpec expectHeader();
589
ResponseSpec expectCookie();
590
ResponseSpec expectBody();
591
<T> ResponseSpec expectBody(Class<T> bodyType);
592
<T> ResponseSpec expectBodyList(Class<T> elementType);
593
ResponseSpec expectCookies();
594
595
// Terminal operations
596
<T> EntityExchangeResult<T> returnResult(Class<T> bodyType);
597
<T> FluxExchangeResult<T> returnResult(ParameterizedTypeReference<T> bodyTypeReference);
598
EntityExchangeResult<Void> returnResult(Void.class);
599
}
600
}
601
```
602
603
## Transaction Testing
604
605
### Transaction Annotations
606
607
```java { .api }
608
// Indicates that transaction should be rolled back after test method
609
@Target({ElementType.TYPE, ElementType.METHOD})
610
@Retention(RetentionPolicy.RUNTIME)
611
@Documented
612
public @interface Rollback {
613
boolean value() default true;
614
}
615
616
// Indicates that transaction should be committed after test method
617
@Target({ElementType.TYPE, ElementType.METHOD})
618
@Retention(RetentionPolicy.RUNTIME)
619
@Documented
620
public @interface Commit {
621
}
622
623
// Defines SQL scripts to execute before/after test methods
624
@Target({ElementType.TYPE, ElementType.METHOD})
625
@Retention(RetentionPolicy.RUNTIME)
626
@Documented
627
@Repeatable(SqlGroup.class)
628
public @interface Sql {
629
630
@AliasFor("scripts")
631
String[] value() default {};
632
633
@AliasFor("value")
634
String[] scripts() default {};
635
636
ExecutionPhase executionPhase() default ExecutionPhase.BEFORE_TEST_METHOD;
637
638
SqlConfig config() default @SqlConfig;
639
640
// Execution phase enum
641
enum ExecutionPhase {
642
BEFORE_TEST_METHOD, AFTER_TEST_METHOD
643
}
644
}
645
646
// Configuration for @Sql
647
@Target({})
648
@Retention(RetentionPolicy.RUNTIME)
649
@Documented
650
public @interface SqlConfig {
651
652
String dataSource() default "";
653
654
String transactionManager() default "";
655
656
TransactionMode transactionMode() default TransactionMode.DEFAULT;
657
658
String encoding() default "";
659
660
String separator() default ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
661
662
String commentPrefix() default ScriptUtils.DEFAULT_COMMENT_PREFIX;
663
664
String blockCommentStartDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
665
666
String blockCommentEndDelimiter() default ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
667
668
ErrorMode errorMode() default ErrorMode.DEFAULT;
669
670
// Enums
671
enum TransactionMode { DEFAULT, ISOLATED, INFERRED }
672
enum ErrorMode { DEFAULT, FAIL_ON_ERROR, CONTINUE_ON_ERROR, IGNORE_FAILED_DROPS }
673
}
674
```
675
676
## Practical Usage Examples
677
678
### Basic Integration Testing
679
680
```java { .api }
681
@SpringBootTest
682
@TestPropertySource(locations = "classpath:application-test.properties")
683
@ActiveProfiles("test")
684
class UserServiceIntegrationTest {
685
686
@Autowired
687
private UserService userService;
688
689
@MockBean
690
private EmailService emailService;
691
692
@Autowired
693
private UserRepository userRepository;
694
695
@Test
696
@Transactional
697
@Rollback
698
void shouldCreateUserSuccessfully() {
699
// Given
700
CreateUserRequest request = CreateUserRequest.builder()
701
.username("testuser")
702
.email("test@example.com")
703
.firstName("Test")
704
.lastName("User")
705
.build();
706
707
// When
708
User createdUser = userService.createUser(request);
709
710
// Then
711
assertThat(createdUser).isNotNull();
712
assertThat(createdUser.getId()).isNotNull();
713
assertThat(createdUser.getUsername()).isEqualTo("testuser");
714
assertThat(createdUser.getEmail()).isEqualTo("test@example.com");
715
716
// Verify email service was called
717
verify(emailService).sendWelcomeEmail(createdUser);
718
719
// Verify user was persisted
720
Optional<User> savedUser = userRepository.findById(createdUser.getId());
721
assertThat(savedUser).isPresent();
722
assertThat(savedUser.get().getUsername()).isEqualTo("testuser");
723
}
724
725
@Test
726
@Transactional
727
void shouldThrowExceptionWhenUserAlreadyExists() {
728
// Given - create user first
729
User existingUser = User.builder()
730
.username("duplicate")
731
.email("duplicate@example.com")
732
.build();
733
userRepository.save(existingUser);
734
735
CreateUserRequest request = CreateUserRequest.builder()
736
.username("duplicate")
737
.email("different@example.com")
738
.build();
739
740
// When & Then
741
assertThatThrownBy(() -> userService.createUser(request))
742
.isInstanceOf(UserAlreadyExistsException.class)
743
.hasMessageContaining("User already exists");
744
745
verifyNoInteractions(emailService);
746
}
747
}
748
```
749
750
### Web Layer Testing with MockMvc
751
752
```java { .api }
753
@WebMvcTest(UserController.class)
754
class UserControllerTest {
755
756
@Autowired
757
private MockMvc mockMvc;
758
759
@MockBean
760
private UserService userService;
761
762
@Autowired
763
private ObjectMapper objectMapper;
764
765
@Test
766
void shouldReturnUserWhenValidId() throws Exception {
767
// Given
768
Long userId = 1L;
769
User user = User.builder()
770
.id(userId)
771
.username("testuser")
772
.email("test@example.com")
773
.build();
774
775
when(userService.findById(userId)).thenReturn(user);
776
777
// When & Then
778
mockMvc.perform(get("/api/users/{id}", userId)
779
.contentType(MediaType.APPLICATION_JSON))
780
.andExpect(status().isOk())
781
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
782
.andExpect(jsonPath("$.id").value(userId))
783
.andExpect(jsonPath("$.username").value("testuser"))
784
.andExpect(jsonPath("$.email").value("test@example.com"))
785
.andDo(print());
786
787
verify(userService).findById(userId);
788
}
789
790
@Test
791
void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
792
// Given
793
Long userId = 999L;
794
when(userService.findById(userId))
795
.thenThrow(new UserNotFoundException("User not found"));
796
797
// When & Then
798
mockMvc.perform(get("/api/users/{id}", userId))
799
.andExpect(status().isNotFound())
800
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
801
.andExpect(jsonPath("$.message").value("User not found"))
802
.andExpect(jsonPath("$.code").value("USER_NOT_FOUND"));
803
}
804
805
@Test
806
void shouldCreateUserWhenValidRequest() throws Exception {
807
// Given
808
CreateUserRequest request = CreateUserRequest.builder()
809
.username("newuser")
810
.email("new@example.com")
811
.firstName("New")
812
.lastName("User")
813
.build();
814
815
User createdUser = User.builder()
816
.id(1L)
817
.username("newuser")
818
.email("new@example.com")
819
.firstName("New")
820
.lastName("User")
821
.build();
822
823
when(userService.createUser(any(CreateUserRequest.class)))
824
.thenReturn(createdUser);
825
826
// When & Then
827
mockMvc.perform(post("/api/users")
828
.contentType(MediaType.APPLICATION_JSON)
829
.content(objectMapper.writeValueAsString(request)))
830
.andExpect(status().isCreated())
831
.andExpect(header().exists("Location"))
832
.andExpect(jsonPath("$.id").value(1))
833
.andExpected(jsonPath("$.username").value("newuser"));
834
835
verify(userService).createUser(any(CreateUserRequest.class));
836
}
837
838
@Test
839
void shouldReturnBadRequestWhenValidationFails() throws Exception {
840
// Given
841
CreateUserRequest invalidRequest = CreateUserRequest.builder()
842
.username("") // Invalid - empty username
843
.email("invalid-email") // Invalid email format
844
.build();
845
846
// When & Then
847
mockMvc.perform(post("/api/users")
848
.contentType(MediaType.APPLICATION_JSON)
849
.content(objectMapper.writeValueAsString(invalidRequest)))
850
.andExpect(status().isBadRequest())
851
.andExpect(jsonPath("$.validationErrors").exists())
852
.andExpect(jsonPath("$.validationErrors.username").exists())
853
.andExpect(jsonPath("$.validationErrors.email").exists());
854
855
verifyNoInteractions(userService);
856
}
857
858
@Test
859
void shouldUpdateUserWhenValidRequest() throws Exception {
860
// Given
861
Long userId = 1L;
862
UpdateUserRequest request = UpdateUserRequest.builder()
863
.username("updateduser")
864
.email("updated@example.com")
865
.build();
866
867
User updatedUser = User.builder()
868
.id(userId)
869
.username("updateduser")
870
.email("updated@example.com")
871
.build();
872
873
when(userService.updateUser(eq(userId), any(UpdateUserRequest.class)))
874
.thenReturn(updatedUser);
875
876
// When & Then
877
mockMvc.perform(put("/api/users/{id}", userId)
878
.contentType(MediaType.APPLICATION_JSON)
879
.content(objectMapper.writeValueAsString(request)))
880
.andExpect(status().isOk())
881
.andExpect(jsonPath("$.username").value("updateduser"))
882
.andExpect(jsonPath("$.email").value("updated@example.com"));
883
}
884
885
@Test
886
void shouldReturnUsersWithPagination() throws Exception {
887
// Given
888
List<User> users = Arrays.asList(
889
User.builder().id(1L).username("user1").build(),
890
User.builder().id(2L).username("user2").build()
891
);
892
893
Page<User> userPage = new PageImpl<>(users, PageRequest.of(0, 10), 2);
894
when(userService.findAll(any(Pageable.class))).thenReturn(userPage);
895
896
// When & Then
897
mockMvc.perform(get("/api/users")
898
.param("page", "0")
899
.param("size", "10"))
900
.andExpected(status().isOk())
901
.andExpect(jsonPath("$.content").isArray())
902
.andExpect(jsonPath("$.content.length()").value(2))
903
.andExpect(jsonPath("$.content[0].username").value("user1"))
904
.andExpect(jsonPath("$.totalElements").value(2))
905
.andExpect(header().string("X-Total-Count", "2"));
906
}
907
}
908
```
909
910
### Repository Layer Testing
911
912
```java { .api }
913
@DataJpaTest
914
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
915
class UserRepositoryTest {
916
917
@Autowired
918
private TestEntityManager entityManager;
919
920
@Autowired
921
private UserRepository userRepository;
922
923
@Test
924
void shouldFindUserByUsername() {
925
// Given
926
User user = User.builder()
927
.username("testuser")
928
.email("test@example.com")
929
.firstName("Test")
930
.lastName("User")
931
.build();
932
933
entityManager.persistAndFlush(user);
934
935
// When
936
Optional<User> foundUser = userRepository.findByUsername("testuser");
937
938
// Then
939
assertThat(foundUser).isPresent();
940
assertThat(foundUser.get().getUsername()).isEqualTo("testuser");
941
assertThat(foundUser.get().getEmail()).isEqualTo("test@example.com");
942
}
943
944
@Test
945
void shouldReturnEmptyWhenUserNotFound() {
946
// When
947
Optional<User> foundUser = userRepository.findByUsername("nonexistent");
948
949
// Then
950
assertThat(foundUser).isEmpty();
951
}
952
953
@Test
954
void shouldFindUsersByEmailDomain() {
955
// Given
956
User user1 = User.builder()
957
.username("user1")
958
.email("user1@company.com")
959
.build();
960
961
User user2 = User.builder()
962
.username("user2")
963
.email("user2@company.com")
964
.build();
965
966
User user3 = User.builder()
967
.username("user3")
968
.email("user3@gmail.com")
969
.build();
970
971
entityManager.persist(user1);
972
entityManager.persist(user2);
973
entityManager.persist(user3);
974
entityManager.flush();
975
976
// When
977
List<User> companyUsers = userRepository.findByEmailContaining("@company.com");
978
979
// Then
980
assertThat(companyUsers).hasSize(2);
981
assertThat(companyUsers).extracting(User::getUsername)
982
.containsExactlyInAnyOrder("user1", "user2");
983
}
984
985
@Test
986
@Sql("/test-data/users.sql")
987
void shouldCountActiveUsers() {
988
// When
989
long activeCount = userRepository.countByActiveTrue();
990
991
// Then
992
assertThat(activeCount).isEqualTo(3); // Based on test data
993
}
994
995
@Test
996
void shouldDeleteUsersByCreatedDateBefore() {
997
// Given
998
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(30);
999
1000
User oldUser = User.builder()
1001
.username("olduser")
1002
.email("old@example.com")
1003
.createdDate(cutoffDate.minusDays(1))
1004
.build();
1005
1006
User newUser = User.builder()
1007
.username("newuser")
1008
.email("new@example.com")
1009
.createdDate(cutoffDate.plusDays(1))
1010
.build();
1011
1012
entityManager.persist(oldUser);
1013
entityManager.persist(newUser);
1014
entityManager.flush();
1015
1016
// When
1017
int deletedCount = userRepository.deleteByCreatedDateBefore(cutoffDate);
1018
1019
// Then
1020
assertThat(deletedCount).isEqualTo(1);
1021
1022
List<User> remainingUsers = userRepository.findAll();
1023
assertThat(remainingUsers).hasSize(1);
1024
assertThat(remainingUsers.get(0).getUsername()).isEqualTo("newuser");
1025
}
1026
}
1027
```
1028
1029
### WebFlux Testing with WebTestClient
1030
1031
```java { .api }
1032
@WebFluxTest(ReactiveUserController.class)
1033
class ReactiveUserControllerTest {
1034
1035
@Autowired
1036
private WebTestClient webTestClient;
1037
1038
@MockBean
1039
private ReactiveUserService userService;
1040
1041
@Test
1042
void shouldReturnUserWhenFound() {
1043
// Given
1044
Long userId = 1L;
1045
User user = User.builder()
1046
.id(userId)
1047
.username("testuser")
1048
.email("test@example.com")
1049
.build();
1050
1051
when(userService.findById(userId)).thenReturn(Mono.just(user));
1052
1053
// When & Then
1054
webTestClient.get()
1055
.uri("/api/users/{id}", userId)
1056
.exchange()
1057
.expectStatus().isOk()
1058
.expectHeader().contentType(MediaType.APPLICATION_JSON)
1059
.expectBody(User.class)
1060
.value(returnedUser -> {
1061
assertThat(returnedUser.getId()).isEqualTo(userId);
1062
assertThat(returnedUser.getUsername()).isEqualTo("testuser");
1063
});
1064
}
1065
1066
@Test
1067
void shouldReturnNotFoundWhenUserDoesNotExist() {
1068
// Given
1069
Long userId = 999L;
1070
when(userService.findById(userId)).thenReturn(Mono.empty());
1071
1072
// When & Then
1073
webTestClient.get()
1074
.uri("/api/users/{id}", userId)
1075
.exchange()
1076
.expectStatus().isNotFound();
1077
}
1078
1079
@Test
1080
void shouldCreateUserSuccessfully() {
1081
// Given
1082
CreateUserRequest request = CreateUserRequest.builder()
1083
.username("newuser")
1084
.email("new@example.com")
1085
.build();
1086
1087
User createdUser = User.builder()
1088
.id(1L)
1089
.username("newuser")
1090
.email("new@example.com")
1091
.build();
1092
1093
when(userService.createUser(any(CreateUserRequest.class)))
1094
.thenReturn(Mono.just(createdUser));
1095
1096
// When & Then
1097
webTestClient.post()
1098
.uri("/api/users")
1099
.contentType(MediaType.APPLICATION_JSON)
1100
.bodyValue(request)
1101
.exchange()
1102
.expectStatus().isCreated()
1103
.expectHeader().exists("Location")
1104
.expectBody(User.class)
1105
.value(user -> {
1106
assertThat(user.getId()).isEqualTo(1L);
1107
assertThat(user.getUsername()).isEqualTo("newuser");
1108
});
1109
}
1110
1111
@Test
1112
void shouldStreamUsers() {
1113
// Given
1114
List<User> users = Arrays.asList(
1115
User.builder().id(1L).username("user1").build(),
1116
User.builder().id(2L).username("user2").build(),
1117
User.builder().id(3L).username("user3").build()
1118
);
1119
1120
when(userService.findAll()).thenReturn(Flux.fromIterable(users));
1121
1122
// When & Then
1123
webTestClient.get()
1124
.uri("/api/users/stream")
1125
.accept(MediaType.TEXT_EVENT_STREAM)
1126
.exchange()
1127
.expectStatus().isOk()
1128
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_EVENT_STREAM)
1129
.expectBodyList(User.class)
1130
.hasSize(3)
1131
.contains(users.toArray(new User[0]));
1132
}
1133
1134
@Test
1135
void shouldHandleValidationErrors() {
1136
// Given
1137
CreateUserRequest invalidRequest = CreateUserRequest.builder()
1138
.username("") // Invalid
1139
.build();
1140
1141
// When & Then
1142
webTestClient.post()
1143
.uri("/api/users")
1144
.contentType(MediaType.APPLICATION_JSON)
1145
.bodyValue(invalidRequest)
1146
.exchange()
1147
.expectStatus().isBadRequest()
1148
.expectBody()
1149
.jsonPath("$.validationErrors").exists()
1150
.jsonPath("$.validationErrors.username").exists();
1151
}
1152
}
1153
```
1154
1155
### Custom Test Configuration
1156
1157
```java { .api }
1158
// Test configuration class
1159
@TestConfiguration
1160
public class TestConfig {
1161
1162
@Bean
1163
@Primary
1164
public Clock testClock() {
1165
return Clock.fixed(Instant.parse("2023-01-01T00:00:00Z"), ZoneOffset.UTC);
1166
}
1167
1168
@Bean
1169
public ObjectMapper testObjectMapper() {
1170
ObjectMapper mapper = new ObjectMapper();
1171
mapper.registerModule(new JavaTimeModule());
1172
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
1173
return mapper;
1174
}
1175
1176
@Bean
1177
@Primary
1178
public PasswordEncoder mockPasswordEncoder() {
1179
return Mockito.mock(PasswordEncoder.class);
1180
}
1181
}
1182
1183
// Base test class with common configuration
1184
@SpringBootTest
1185
@TestPropertySource(locations = "classpath:application-test.properties")
1186
@Import(TestConfig.class)
1187
@Transactional
1188
public abstract class BaseIntegrationTest {
1189
1190
@Autowired
1191
protected TestEntityManager entityManager;
1192
1193
@MockBean
1194
protected EmailService emailService;
1195
1196
@BeforeEach
1197
void setUp() {
1198
// Common setup
1199
Mockito.reset(emailService);
1200
}
1201
1202
@AfterEach
1203
void tearDown() {
1204
// Common cleanup
1205
}
1206
1207
protected User createTestUser(String username) {
1208
User user = User.builder()
1209
.username(username)
1210
.email(username + "@example.com")
1211
.build();
1212
return entityManager.persistAndFlush(user);
1213
}
1214
}
1215
1216
// Test slices with custom configuration
1217
@WebMvcTest
1218
@Import({SecurityTestConfig.class, TestConfig.class})
1219
class SecureControllerTest {
1220
1221
@Autowired
1222
private MockMvc mockMvc;
1223
1224
@Test
1225
@WithMockUser(roles = "ADMIN")
1226
void shouldAllowAdminAccess() throws Exception {
1227
mockMvc.perform(get("/admin/users"))
1228
.andExpected(status().isOk());
1229
}
1230
1231
@Test
1232
@WithMockUser(roles = "USER")
1233
void shouldDenyUserAccess() throws Exception {
1234
mockMvc.perform(get("/admin/users"))
1235
.andExpected(status().isForbidden());
1236
}
1237
}
1238
1239
// Parameterized tests
1240
class UserValidationTest {
1241
1242
@ParameterizedTest
1243
@ValueSource(strings = {"", " ", "ab", "a".repeat(51)})
1244
void shouldRejectInvalidUsernames(String username) {
1245
CreateUserRequest request = CreateUserRequest.builder()
1246
.username(username)
1247
.email("test@example.com")
1248
.build();
1249
1250
Set<ConstraintViolation<CreateUserRequest>> violations = validator.validate(request);
1251
1252
assertThat(violations).isNotEmpty();
1253
assertThat(violations).anyMatch(v -> v.getPropertyPath().toString().equals("username"));
1254
}
1255
1256
@ParameterizedTest
1257
@CsvSource({
1258
"test@example.com, true",
1259
"invalid-email, false",
1260
"@example.com, false",
1261
"test@, false",
1262
"test.email@domain.co.uk, true"
1263
})
1264
void shouldValidateEmailFormats(String email, boolean shouldBeValid) {
1265
CreateUserRequest request = CreateUserRequest.builder()
1266
.username("testuser")
1267
.email(email)
1268
.build();
1269
1270
Set<ConstraintViolation<CreateUserRequest>> violations = validator.validate(request);
1271
boolean hasEmailViolation = violations.stream()
1272
.anyMatch(v -> v.getPropertyPath().toString().equals("email"));
1273
1274
assertThat(hasEmailViolation).isEqualTo(!shouldBeValid);
1275
}
1276
}
1277
```
1278
1279
### Performance and Load Testing
1280
1281
```java { .api }
1282
// Performance test with JMH (requires jmh dependency)
1283
@BenchmarkMode(Mode.Throughput)
1284
@Warmup(iterations = 2)
1285
@Measurement(iterations = 5)
1286
@State(Scope.Benchmark)
1287
public class UserServicePerformanceTest {
1288
1289
private UserService userService;
1290
private CreateUserRequest request;
1291
1292
@Setup
1293
public void setup() {
1294
// Initialize Spring context
1295
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
1296
userService = context.getBean(UserService.class);
1297
1298
request = CreateUserRequest.builder()
1299
.username("testuser")
1300
.email("test@example.com")
1301
.build();
1302
}
1303
1304
@Benchmark
1305
public User createUser() {
1306
return userService.createUser(request);
1307
}
1308
}
1309
1310
// Concurrent testing
1311
@SpringBootTest
1312
class ConcurrentUserServiceTest {
1313
1314
@Autowired
1315
private UserService userService;
1316
1317
@Test
1318
void shouldHandleConcurrentUserCreation() throws InterruptedException {
1319
int numberOfThreads = 10;
1320
int usersPerThread = 100;
1321
CountDownLatch latch = new CountDownLatch(numberOfThreads);
1322
AtomicInteger successCount = new AtomicInteger(0);
1323
AtomicInteger errorCount = new AtomicInteger(0);
1324
1325
ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads);
1326
1327
for (int i = 0; i < numberOfThreads; i++) {
1328
final int threadId = i;
1329
executor.submit(() -> {
1330
try {
1331
for (int j = 0; j < usersPerThread; j++) {
1332
CreateUserRequest request = CreateUserRequest.builder()
1333
.username("user_" + threadId + "_" + j)
1334
.email("user_" + threadId + "_" + j + "@example.com")
1335
.build();
1336
1337
try {
1338
userService.createUser(request);
1339
successCount.incrementAndGet();
1340
} catch (Exception e) {
1341
errorCount.incrementAndGet();
1342
}
1343
}
1344
} finally {
1345
latch.countDown();
1346
}
1347
});
1348
}
1349
1350
latch.await(30, TimeUnit.SECONDS);
1351
executor.shutdown();
1352
1353
int expectedTotal = numberOfThreads * usersPerThread;
1354
assertThat(successCount.get() + errorCount.get()).isEqualTo(expectedTotal);
1355
assertThat(errorCount.get()).isLessThan(expectedTotal * 0.1); // Less than 10% errors
1356
}
1357
}
1358
```
1359
1360
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.