or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aop.mdcore-container.mddata-access.mdindex.mdintegration.mdmessaging.mdreactive-web.mdtesting.mdweb-framework.md

testing.mddocs/

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.