Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing Spring Boot Applications Demystified (J...

Testing Spring Boot Applications Demystified (JUG Barcelona 2025)

Testing plays a crucial role in software development, allowing you to catch bugs, ensure your code functions as intended, and provide confidence in the reliability and stability of your applications.

However, testing Spring Boot applications can sometimes feel like navigating a labyrinth of challenges – from managing dependencies and external systems to crafting effective tests that reflect the behavior of your code.

Whether you’re a seasoned developer or just starting your journey with Spring Boot, this talk is designed to unravel the complexities and pitfalls of testing Spring Boot applications and empower you to become more productive and confident in your testing efforts.

In this talk, we will demystify testing Spring Boot applications by providing you with clear explanations, practical insights, and actionable best practices. Philip will guide you through the common pitfalls faced by developers, share time-tested recipes for success, and equip you with the tools and knowledge you need to write comprehensive and effective tests.

The code is available at: https://github.com/rieckpil/talks/tree/main/testing-spring-boot-applications-demystified

Avatar for Philip Riecks

Philip Riecks

May 21, 2025
Tweet

More Decks by Philip Riecks

Other Decks in Programming

Transcript

  1. Testing Spring Boot Applications Demystified Best Practices, Common Pitfalls, and

    Real-World Strategies Java User Group Barcelona 20.05.2025 Philip Riecks - PragmaTech GmbH - @rieckpil
  2. Getting Used To Testing At Work Testing Spring Boot Applications

    Demystified @ JUG Barcelona 20.05.2025 4
  3. Goals For This Talk Lay the foundation for your Spring

    Boot testing success Introduction to Spring Boot’s excellent test support Showcase a mix of best practices and early pitfalls Convince you that testing is not an afterthought Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 5
  4. About Self-employed IT consultant from Herzogenaurach, Germany (Bavaria) Blogging &

    content creation for more than five years. Since three years with a focus on testing Java and specifically Spring Boot applications Founder of PragmaTech GmbH - Enabling Developers to Frequently Deliver Software with More Confidence Enjoys writing tests @rieckpil on various platforms Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 6
  5. Agenda Introduction Testing with Spring Boot Spring Boot Testing 101

    Unit Testing Sliced Testing Integration Testing Spring Boot Testing Best Practices Common Spring Boot Testing Pitfalls to Avoid Summary & Outlook Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 7
  6. Naming Things Is Hard Unit Tests Integration Tests E2E Tests

    Regression Test Functional Test System Tests Performance Tests Black Box Test White Box Test User Acceptance Tests Smoke Tests Load Tests Stress Tests Exploratory Tests Integrated Test Fast Test Mutation Tests Contract Tests Property-based Tests Boundary Value Testing Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 9
  7. My Pragmatic Test Name Approach 1. Unit Tests: Tests that

    verify the functionality of a single, isolated component (like a method or class) by mocking or stubbing all external dependencies. 2. Integration Tests: Tests that verify interactions between two or more components work correctly together, with real implementations replacing some mocks. 3. E2E: Tests that validate the entire application workflow from start to finish, simulating real user scenarios across all components and external dependencies. Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 10
  8. Running ./mvnw verify validate compile test-compile test maven-surefire-plugin:test (Unit Tests)

    package pre-integration-test integration-test maven-failsafe-plugin:integration-tes (Runs Integration Tests) post-integration-test verify maven-failsafe-plugin:verify (Checks Integration Test Results) Legend: Surefire Execution Phase Failsafe Execution Phases Maven Build Lifecycle Maven Surfire Plugin for unit tests: default postfix *Test (e.g. CustomerTest ) Maven Failsafe Plugin for integration tests: default postfix *IT (e.g. CheckoutIT ) Reason for splitting: different parallelization options, better organisation Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 11
  9. Spring Boot Starter Test aka. "Testing Swiss Army Knife" Batteries-included

    for testing Dependency management for: JUnit Jupiter Mockito AssertJ Awaitility etc. We can manually override the dependency versions Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 12
  10. [INFO] +- org.springframework.boot:spring-boot-starter-test:jar:3.4.5:test [INFO] | +- org.springframework.boot:spring-boot-test:jar:3.4.5:test [INFO] | +-

    org.springframework.boot:spring-boot-test-autoconfigure:jar:3.4.5:test [INFO] | +- com.jayway.jsonpath:json-path:jar:2.9.0:test [INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.2:test [INFO] | | \- jakarta.activation:jakarta.activation-api:jar:2.1.3:test [INFO] | +- net.minidev:json-smart:jar:2.5.2:test [INFO] | | \- net.minidev:accessors-smart:jar:2.5.2:test [INFO] | | \- org.ow2.asm:asm:jar:9.7.1:test [INFO] | +- org.assertj:assertj-core:jar:3.26.3:test [INFO] | | \- net.bytebuddy:byte-buddy:jar:1.15.11:test [INFO] | +- org.awaitility:awaitility:jar:4.3.0:test [INFO] | +- org.hamcrest:hamcrest:jar:2.2:test [INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.11.4:test [INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.11.4:test [INFO] | | | +- org.junit.platform:junit-platform-commons:jar:1.11.4:test [INFO] | | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test [INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.11.4:test [INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.11.4:test [INFO] | | \- org.junit.platform:junit-platform-engine:jar:1.11.4:test [INFO] | +- org.mockito:mockito-core:jar:5.17.0:test [INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.15.11:test [INFO] | | \- org.objenesis:objenesis:jar:3.3:test [INFO] | +- org.mockito:mockito-junit-jupiter:jar:5.17.0:test [INFO] | +- org.skyscreamer:jsonassert:jar:1.5.3:test [INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test [INFO] | +- org.springframework:spring-core:jar:6.2.6:compile [INFO] | | \- org.springframework:spring-jcl:jar:6.2.6:compile [INFO] | +- org.springframework:spring-test:jar:6.2.6:test [INFO] | \- org.xmlunit:xmlunit-core:jar:2.10.0:test Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 13
  11. Unit Testing with Spring Boot Provide collaborators from outside (dependency

    injection) -> no new inside your code Develop small, single responsibility classes Test only the public API of your class Verify behavior not implementation details TDD can help design (better) classes Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 14
  12. Avoid Static Method Access @Service public class BirthdayService { public

    boolean isTodayBirthday(LocalDate birthday) { LocalDate today = LocalDate.now(); return today.getMonth() == birthday.getMonth() && today.getDayOfMonth() == birthday.getDayOfMonth(); } } Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 15
  13. Better Alternative @Service public class BirthdayServiceWithClock { private final Clock

    clock; public BirthdayServiceWithClock(Clock clock) { this.clock = clock; } public boolean isTodayBirthday(LocalDate birthday) { LocalDate today = LocalDate.now(clock); return today.getMonth() == birthday.getMonth() && today.getDayOfMonth() == birthday.getDayOfMonth(); } } Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 16
  14. @Test void shouldReturnTrueWhenTodayIsBirthday() { // Arrange LocalDate fixedDate = LocalDate.of(2025,

    5, 15); Clock fixedClock = Clock.fixed( fixedDate.atStartOfDay(ZONE_ID).toInstant(), ZONE_ID ); BirthdayServiceWithClock cut = new BirthdayServiceWithClock(fixedClock); LocalDate birthday = LocalDate.of(1990, 5, 15); // Same month and day // Act boolean result = cut.isTodayBirthday(birthday); // Assert assertThat(result).isTrue(); } Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 17
  15. Check Your Imports Nothing Spring-related here Rely only on JUnit,

    Mockito and an assertion library import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import static org.assertj.core.api.Assertions.assertThat; Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 18
  16. Unify Test Structure Use a consistent test method naming: givenWhenThen,

    shouldWhen, etc. Structure test for the Arrange/Act/Assert test setup @Test void should_When_() { // Arrange // ... setting up objects, data, collaborators, etc. // Act // ... performing the action to be tested on the class under test // Assert // ... verifying the expected outcome } Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 19
  17. Unit Testing Has Limits Request Mapping: Does /api/users/{id} actually resolve

    to your desired method? Validation: Will incomplete request bodys result in a 400 bad request or return an accidental 200? Serialization: Are your JSON objects serialized and deserialized correctly? Headers: Are you setting Content-Type or custom headers correctly? Security: Are your Spring Security configuration and other authorization checks enforced? Database: Can we effectively map our complex JPA entity to a database table? etc. Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 20
  18. Slicing Example: @WebMvcTest Testing your web layer in isolation and

    only load the beans you need MockMvc : Mocked servlet environment with HTTP semantics @WebMvcTest(CustomerController.class) class CustomerControllerTest { @Autowired private MockMvc mockMvc; @MockitoBean private CustomerService customerService; } See WebMvcTypeExcludeFilter for included Spring beans Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 24
  19. Starting the Entire Context Provide external infrastructure with Testcontainers Start

    Tomcat with: @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) Consider WireMock/MockServer for stubbing external HTTP services Test controller endpoints via: MockMvc , WebTestClient , TestRestTemplate Speed up builds with Spring Test TestContext caching Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 28
  20. Spring Test TestContext Caching Part of Spring Test (automatically part

    of every Spring Boot project via spring-boot- starter-test ) Spring Test caches an already started Spring ApplicationContext for later reuse Cache retrieval is usually faster than a cold context start Configurable cache size (default is 32) with LRU (least recently used) strategy Speed up your build: Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 29
  21. Caching is King Spring Test Context Caching Test Execution OrderIT

    (Requires Context Config X) 1. Create Context 1 PaymentIT (Requires Context Config X) 2. Reuse Context 1 FulfillmentIT (Requires Context Config Y) 3. Create Context 2 Context Cache State TestContext Cache Context 1 (for Config X) (Created by OrderIT) (Reused by PaymentIT) Context 2 (for Config Y) (Created by FulfillmentIT) Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 30
  22. How the Cache Key is Built This goes into the

    cache key ( MergedContextConfiguration ): activeProfiles ( @ActiveProfiles ) contextInitializersClasses ( @ContextConfiguration ) propertySourceLocations ( @TestPropertySource ) propertySourceProperties ( @TestPropertySource ) contextCustomizer ( @MockitoBean , @MockBean , @DynamicPropertySource , ...) Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 31
  23. Make the Most of the Caching Feature Avoid @DirtiesContext when

    possible, especially at AbstractIntegrationTest classes Understand how the cache key is built Monitor and investigate the context restarts Align the number of unique context configurations for your test suite Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 35
  24. Best Practice 1: Test Parallelization Goal: Reduce build time and

    get faster feedback Requirements: No shared state No dependency between tests and their execution order No mutation of global state Two ways to achieve this: Fork a new JVM with Surefire/Failsafe and let it run in parallel -> more resources but isolated execution Use JUnit Jupiter's parallelization mode and let it run in the same JVM with multiple threads Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 37
  25. Java Test Parallelization Options Maven Surefire Fork-Based (Multiple JVMs) JUnit

    Jupiter Thread-Based (Single JVM) JVM Fork 1 Test Class A Test Class B Test Class C JVM Fork 2 Test Class D Test Class E Test Class F Single JVM Thread Pool Thread 1 Test Method A1 Test Method B1 Test Class C Thread 2 Test Method A2 Test Method B2 Test Class D Maven (forkCount) Features: Each fork is a separate JVM process Isolated memory spaces Higher memory overhead High isolation (one DB per fork) Configuration: forkCount, reuseForks JUnit Jupiter Features: Uses threads within a single JVM Shared memory space Lower memory overhead Configuration: junit.jupiter.execution.parallel.* Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 38
  26. Best Practice 2: Get Help from AI Diffblue Cover: #1

    AI Agent for unit testing complex Java code at scale Agent vs. Assistant LLMs: ChatGPT, Claude, Gemini, etc. Claude Code TDD with an LLM? (Not AI but still useful) OpenRewrite for migrations Clearly define your requirements in e.g. claude.md or cursor rule files Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 39
  27. Best Practice 3: Try Mutation Testing Having high code coverage

    might give you a false sense of security Mutation Testing with PIT Beyond Line Coverage: Traditional tools like JaCoCo show which code runs during tests, but PIT verifies if your tests actually detect when code behaves incorrectly by introducing "mutations" to your source code. Quality Guarantee: PIT automatically modifies your code (changing conditionals, return values, etc.) to ensure your tests fail when they should, revealing blind spots in seemingly comprehensive test suites. Considerations for bigger projects: only run on the new code diffs, not on the whole codebase Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 40
  28. PIT Mutation Testing Example Original Code public int calculateDiscount( int

    amount, boolean loyal) { if (amount > 1000 && loyal) { return amount / 10; } return 0; } Mutations Created by PIT // Mutation 1: Change && to || if (amount > 1000 || loyal) { return amount / 10; // Mutation 2: Change > to >= if (amount >= 1000 && loyal) { return amount / 10; // Mutation 3: Return constant if (amount > 1000 && loyal) { return 0; JUnit Tests @Test void testNormalDiscount() { int result = discount(2000, true); assertEquals(200, result); } // Missing tests for edge cases PIT Mutation Test Results Mutation 1 ( &&→||): SURVIVED Mutation 2 (>→>=): SURVIVED Mutation 3 (return→0): KILLED Mutation Score: 33% Action Required: Add tests for boundary cases (1000, loyal) and different combinations of inputs Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 41
  29. Common Spring Boot Testing Pitfalls to Avoid Testing Spring Boot

    Applications Demystified @ JUG Barcelona 20.05.2025 42
  30. Testing Pitfall 1: @SpringBootTest Obsession The name could apply it's

    a one size fits all solution, but it isn't It comes with costs: starting the (entire) application context Useful for integration tests that verify the whole application but not for testing a single service in isolation Start with unit tests, see if sliced tests are applicable and only then use @SpringBootTest Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 43
  31. Testing Pitfall 2: @MockitoBean vs. @MockBean vs. @Mock @MockBean is

    a Spring Boot specific annotation that replaces a bean in the application context with a Mockito mock @MockBean is deprecated in favor of the new @MockitoBean annotation @Mock is a Mockito annotation, only for unit tests Golden Mockito Rules: Do not mock types you don't own Don't mock value objects Don't mock everything Show some love with your tests Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 45
  32. Testing Pitfall 3: JUnit 4 vs. JUnit 5 You can

    mix both versions in the same project but not in the same test class Browsing through the internet (aka. StackOverflow/blogs/LLMs) for solutions, you might find test setups that are still for JUnit 4 Easily import the wrong @Test and you end up wasting one hour because the Spring context does not work as expected Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 46
  33. JUnit 4 JUnit 5 @Test from org.junit @Test from org.junit.jupiter.api

    @RunWith @ExtendWith/@RegisterExtension @ClassRule/@Rule - @Before @BeforeEach @Ignore @Disabled @Category @Tag Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 47
  34. Summary & Outlook Spring Boot applications come with batteries-included for

    testing Spring and Spring Boot provides many excellent testing features Mature & rich Java testing ecosystem Consider the context caching feature for fast builds Get help from AI Still many new features are coming: @ServiceConnection , Testcontainers support, Docker Compose support, more AssertJ integrations, etc. Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 48
  35. What's Next? Online Course: Testing Spring Boot Applications Masterclass (on-demand,

    12 hours, 130+ modules) eBook: 30 Testing Tools and Libraries Every Java Developer Must Know eBook: Stratospheric - From Zero to Production with AWS Spring Boot testing workshops (in- house/remote/hybrid) Consulting offerings, e.g. the Test Maturity Assessment Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025 49
  36. Joyful Testing! Reach out any time via: LinkedIn (Philip Riecks)

    X (@rieckpil) Mail ([email protected]) Testing Spring Boot Applications Demystified @ JUG Barcelona 20.05.2025