When I started learning Spring, I noticed something about many of the tutorials I found.
They often showed how to use a feature, but not always why it worked, what was happening behind the scenes, or how to test it properly.
I learn best by building things and documenting what I discover, so I started a project called Spring by Example V2.
The idea is simple: instead of building one large application, I'm creating a collection of small, focused examples where each example explores a specific Spring concept with clear explanations, tests, and documentation.
I'm happy to say that I've now completed Module 05 - Spring Testing. 🎉
This module explored how to test Spring applications at different levels, from loading application contexts and testing individual web layers to verifying security behavior, database interactions, and integration with external resources.
The module covers:
- Spring TestContext Framework
@SpringJUnitConfig@SpringBootTest- Spring MVC Testing
- Spring Test Slices
MockMvc- Transaction Testing
- Spring Security Testing
- Integration Testing
- Testcontainers
- Application Context Testing
- Context Caching
One thing I particularly enjoyed about this module was understanding that testing a Spring application isn't just about writing assertions. It's also about knowing what you're testing, which parts of the application you're loading, and how to make tests reliable and maintainable.
I started with the Spring TestContext Framework, exploring how Spring integrates with testing frameworks such as JUnit Jupiter to manage application contexts, dependency injection, and the test lifecycle.
From there, I explored @SpringJUnitConfig and @SpringBootTest.
These annotations helped me understand the difference between testing with a specific Spring configuration and loading a full Spring Boot application context.
For example:
@SpringJUnitConfig(AppConfig.class)
class GreetingServiceTest {
@Autowired
private GreetingService greetingService;
@Test
void returnsGreeting() {
assertEquals("Hello, Moses!", greetingService.greet("Moses"));
}
}
The important idea is that Spring can provide the configured dependencies directly to the test, allowing us to verify application behavior within a Spring-managed environment.
Next came Spring MVC testing and test slices.
I explored how @WebMvcTest allows us to focus on the web layer without loading the entire application context.
Combined with MockMvc, this makes it possible to test HTTP requests, controller behavior, response status codes, and returned content without starting a real web server.
For example:
@WebMvcTest(GreetingController.class)
class GreetingControllerTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private GreetingService greetingService;
@Test
void returnsGreeting() throws Exception {
when(greetingService.greet("Moses"))
.thenReturn("Hello, Moses!");
mockMvc.perform(get("/greetings")
.param("name", "Moses"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, Moses!"));
}
}
This approach helped me appreciate the value of testing a specific application layer while replacing its dependencies with controlled test doubles.
I also explored transaction testing.
Transactions are important in application development because they help maintain data consistency when multiple operations must succeed or fail together.
Testing transactional behavior helps verify that database changes behave as expected, including rollback scenarios where appropriate.
Spring Security testing was another important part of the module.
Security behavior needs to be tested just as carefully as business logic. It's not enough for an endpoint to return the expected response; we also need to verify that authentication and authorization rules are enforced correctly.
I then moved into integration testing.
Unlike tests that isolate a particular layer, integration tests help verify that multiple application components work together as expected.
This led to Testcontainers, which introduces another useful approach: running tests against real instances of dependencies, such as databases, using containers.
That distinction matters because an in-memory database or a mocked dependency may not reproduce every behavior of the actual system used in production.
I also explored application context testing and finished the module with context caching.
This was particularly interesting because it connected test correctness with test performance.
Creating a Spring application context can involve configuration processing, bean creation, and dependency injection. Repeating that work unnecessarily can make a test suite slow.
Spring's TestContext Framework addresses this by caching and reusing compatible application contexts across tests.
For example:
ApplicationContext firstContext =
loadContext(FirstContextTest.class);
ApplicationContext secondContext =
loadContext(SecondContextTest.class);
assertSame(firstContext, secondContext);
When both test classes use compatible configuration metadata, Spring can reuse the same application context.
I also explored @DirtiesContext, which tells Spring that a context should no longer be reused from the cache.
This helped me understand an important balance: context caching improves performance, but tests must still be designed with proper isolation in mind.
A shared application context doesn't automatically reset the state of every singleton bean between tests.
Looking back, this module reinforced an important lesson for me: good tests don't just tell us whether something works; they help us understand where it works, how its dependencies behave, and whether the application can be trusted when its components interact.
The goal isn't simply to learn how to write:
@Test
void shouldWork() {
// ...
}
The bigger goal is to understand the different testing strategies available in Spring, when to use each one, and how to build confidence in application behavior without making the test suite unnecessarily slow or complicated.
The project is open source, and I'm continuing to build it as I learn:
🔗 GitHub: https://gith.ub/dsAQrAU5
🔗 GitLab: https://gitl.ab/gfDEyGH3
Five modules completed, and each one has been another opportunity to explore the Spring ecosystem in more depth. 🚀
Next up: Module 06 - Capstone Application - bringing together concepts from the preceding modules to build a cohesive, production-oriented application.
One focused example at a time. 🚀
If you've worked with Spring testing before, which testing approach do you find most valuable in your day-to-day development - @WebMvcTest, @SpringBootTest, integration testing, Testcontainers, or something else?
Top comments (0)