Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Spring Boot Testing Mastery
Testing is what separates “demo code” from production code. Spring has a powerful testing framework. Real-world analogy: Think of the test pyramid like quality control in car manufacturing. Unit tests are like testing individual components on a bench — does the brake pad grip? Does the piston fire? These are fast, cheap, and you run thousands of them. Integration tests are like putting the engine, transmission, and brakes together on a test rig — do they work as a system? Fewer of these, more expensive, but they catch wiring problems that component tests miss. E2E tests are like a full test drive on a track — the most realistic, but also the slowest and most expensive. You run a handful, not hundreds.1. The Test Pyramid
- Unit Tests: Test a single class in isolation. Use Mocks.
- Integration Tests: Test multiple components together (e.g., Controller + Service + DB).
- E2E Tests: Full system test (Browser automation, etc.).
2. Unit Testing with JUnit 5 & Mockito
No Spring context. Pure Java testing.@Mock: Create a mock object. All methods return default values (null, 0, false) unless you stub them.when(...).thenReturn(...): Define mock behavior (“when this method is called, return this”).verify(...): Assert that a method was called (and how many times). Tests behavior, not just state.- AssertJ (
assertThat): Fluent assertions (better than JUnit’sassertEquals). Reads like English:assertThat(list).hasSize(3).contains("a", "b").
3. Slice Testing (@WebMvcTest, @DataJpaTest)
Spring Boot provides Test Slices that load ONLY the relevant parts of the context.@WebMvcTest (Controller Layer)
@WebMvcTest loads ONLY the web layer: controllers, @ControllerAdvice, @JsonComponent, filters, and WebMvcConfigurer. No services, no repositories, no database. This is why it starts in 2-3 seconds instead of 15-30.
MockMvc: Simulates HTTP calls without starting a server. Tests serialization, validation, status codes, and headers.@MockBean: Replaces a real bean with a mock in the Spring test context. Scoped to the test class.
@MockBean creates the mock inside the Spring context (replaces the real bean). @Mock creates a standalone Mockito mock with no Spring involvement. Use @Mock for unit tests (no Spring context), @MockBean for slice/integration tests (Spring context is running).
@DataJpaTest (Repository Layer)
4. Integration Testing (@SpringBootTest)
Loads the full application context. Closest to production.5. Testcontainers (Real Databases)
H2 is convenient, but it is NOT PostgreSQL. Differences in SQL dialects, JSON column handling, window functions, and constraint enforcement can cause bugs that pass H2 tests but fail in production. If your production database is Postgres, your integration tests should run against Postgres. Testcontainers spins up a real Postgres container via Docker. Your test starts a disposable database, runs queries against it, and tears it down when done. No shared state between test runs, no “works on my machine” problems.Dependency
Usage
reuse feature or Spring Boot 3.1+‘s @ServiceConnection annotation with a shared container:
6. Mocking External APIs (WireMock)
If your service calls an external HTTP API, you don’t want to hit the real API in tests.7. Best Practices
- Don’t overuse
@SpringBootTest: It loads the entire application context and can take 15-30 seconds to start. Use slices (@WebMvcTest,@DataJpaTest) when you only need a specific layer. Reserve@SpringBootTestfor true end-to-end integration tests. - Use Testcontainers for DB tests: H2 is fine for unit tests that barely touch SQL, but integration tests should use the real database engine. The SQL dialect differences between H2 and PostgreSQL have caused more production bugs than most teams care to admit.
- Test behavior, not implementation: Test what the method does (given this input, expect this output), not how it does it. Do not test private methods. If you feel compelled to, it usually means the private method should be extracted into its own class.
- Avoid
Thread.sleep()in tests: UseAwaitilityfor async assertions. It polls a condition with configurable timeout and interval instead of hoping 500ms is enough. - Use
@Transactionalon test classes with caution: Spring rolls back transactions in tests by default, which is great for isolation. But it can mask bugs — your production code might not be@Transactionalwhen it should be, and the test-level transaction hides the problem. For integration tests that verify transactional behavior, do not use@Transactionalon the test class. - Name tests descriptively:
shouldReturn404WhenUserNotFoundtells the next developer exactly what broke.testUser3tells them nothing.
Interview Deep-Dive
What is the difference between @Mock, @MockBean, and @SpyBean? When would you use each, and what are the performance implications?
What is the difference between @Mock, @MockBean, and @SpyBean? When would you use each, and what are the performance implications?
Strong Answer:
@Mock(Mockito): Creates a pure Mockito mock with no Spring context. Used with@ExtendWith(MockitoExtension.class). All methods return null/0/false by default. Fastest option because no ApplicationContext is loaded. Use when testing a single class in isolation.@MockBean(Spring Boot Test): Replaces a real bean in the ApplicationContext with a Mockito mock. Used in slice tests (@WebMvcTest,@DataJpaTest). Performance catch: every unique combination of@MockBeandeclarations creates a new ApplicationContext. If Test A mocks ServiceX and Test B mocks ServiceY, Spring boots a new context for each. With 200 test classes, inconsistent mocking can add 30+ minutes.@SpyBean(Spring Boot Test): Wraps the real bean with a Mockito spy. Real implementation runs by default; specific methods can be overridden. Use when you want mostly real behavior but need to stub one method (like an external API call).- Strategy: create abstract base test classes per slice with standardized
@MockBeandeclarations. All controller tests extendBaseControllerTest, sharing one cached context. This alone can cut test suite time by 60-70%.
Explain test slicing in Spring Boot. Why does @WebMvcTest exist when @SpringBootTest can do everything?
Explain test slicing in Spring Boot. Why does @WebMvcTest exist when @SpringBootTest can do everything?
Strong Answer:
@SpringBootTestloads everything: all beans, auto-configurations, embedded server, DB connections. Typical startup: 5-15 seconds per unique context. Multiply by 100 test classes and you spend 25 minutes on context startup alone.- Slices load only the relevant layer.
@WebMvcTest(UserController.class)loads the specified controller,@ControllerAdvice,WebMvcConfigurer, JSON converters, and MockMvc. No@Service,@Repository, or database config. Boots in 1-3 seconds. - Other slices:
@DataJpaTest(repositories, EntityManager, H2),@DataMongoTest,@JsonTest(serialization),@WebFluxTest. Each auto-configures only what its layer needs. - Architectural benefit: slices enforce layer boundaries. If your controller test needs a repository, that is a code smell — your controller reaches through the service layer. The slice fails because repos are not loaded, forcing you to fix the design.
BaseControllerTest, BaseRepositoryTest) with consistent @MockBean and @TestPropertySource declarations. All controller tests share one cached context. Group execution: run all @WebMvcTest first, then @DataJpaTest, then @SpringBootTest. For singleton Testcontainers, use @ServiceConnection with a shared container in a @TestConfiguration — one container for the entire suite, 3 seconds total overhead instead of 3 seconds per class.Why should you use Testcontainers instead of H2 for database integration tests? Give a concrete example of a bug H2 would miss.
Why should you use Testcontainers instead of H2 for database integration tests? Give a concrete example of a bug H2 would miss.
Strong Answer:
- H2’s “compatibility mode” covers basic SQL syntax, not engine behavior. It does not support PostgreSQL JSONB operators (
->,->>,@>), array types,LISTEN/NOTIFY, partial indexes, or exclusion constraints. - Concrete bug: your app uses a JSONB column with GIN index and
WHERE metadata @> '{"status": "active"}'. H2 either fails with syntax error or you rewrite the query for compatibility — meaning you are not testing your actual query. A subtle JSONB path typo passes H2 but fails in Postgres. - Case sensitivity: PostgreSQL string comparisons are case-sensitive by default. H2 may handle
LIKEand=differently. Tests pass because'John' = 'john'on H2, but fail in production. - DDL differences: Hibernate generates different DDL for H2 vs. PostgreSQL. Column types, auto-increment strategies, Flyway migrations with Postgres-specific SQL simply do not run on H2.
- Cost: first run downloads the image (~200MB, one-time). Subsequent runs take 3-5 seconds for container startup — slower than H2’s milliseconds but catches bugs H2 fundamentally cannot detect.
PostgreSQLContainer in a shared @TestConfiguration with @ServiceConnection. One container boots once, all tests share it. Combined with @Transactional rollback, you get full isolation without restart. Total overhead: 3 seconds for the entire suite. In CI, pre-pull Docker images in the pipeline setup step for near-instant startup.How would you test a service that calls an external REST API? Compare WireMock, MockRestServiceServer, and @MockBean approaches.
How would you test a service that calls an external REST API? Compare WireMock, MockRestServiceServer, and @MockBean approaches.
Strong Answer:
@MockBeanthe client interface (e.g., Feign): Fastest, simplest. You stub the Java method. But you skip HTTP serialization, error codes, headers, and timeouts. If the real API returns 503 with Retry-After and your client should handle it,@MockBeancannot verify this.MockRestServiceServer(Spring Test): InterceptsRestTemplatecalls at the HTTP client level. Verifies correct HTTP method, URL, headers, body. Tightly coupled toRestTemplate— does not work withWebClientor Feign.- WireMock: Starts a real HTTP server on a random port. Your code makes real HTTP calls. Tests the full stack: serialization, connection handling, timeouts, retries, error codes. Can simulate slow responses (
withFixedDelay(5000)), connection resets, malformed JSON. Works with any HTTP client. - Recommendation: WireMock for HTTP client integration tests. It catches real bugs: wrong Content-Type header, missing retry logic for 429, timeout configured at 30s instead of 5s. Use
@MockBeanonly in controller tests where you test controller behavior, not client behavior.
slidingWindowSize. Assert subsequent calls return fallback values. Verify via WireMock request count that after the circuit opens, zero additional requests hit the mock server. After waitDurationInOpenState, verify exactly one probe request (half-open). On success, verify the circuit closes. This requires exercising the real Resilience4j state machine — impossible with @MockBean.