Skip to main content

Deterministic Spring Boot Testing Guide

Key Takeaways
  • The "Third Way": Deterministic Replay instead of fragile unit tests or slow integration tests.
  • Full Chain: Exercises Filters, Controllers, Validation, Services, and Repositories for real.
  • Mocking: Eliminates fragile @MockBean boilerplate by virtualizing boundaries from traces.
  • Coverage: Catches expensive bug classes like transaction bypasses and serialization drift.

Testing Spring Boot microservices often falls into two traps:

  1. Unit Tests are too fake: Mocking every bean (@MockBean) means you aren't testing real logic.
  2. Integration Tests are too slow: Spinning up full contexts and DBs for every test takes minutes.

BitDive offers a Third Way: Deterministic Replay.

Why Full Spring Context Matters​

The goal is to test one Spring Boot service as a deployed system within its own boundaries. The full internal chain runs for real:

HTTP Entry → Filters / Security → Controllers → @Validated → Services → @Transactional → Repositories / SQL → Response Assembly

Only dependencies that cross the service boundary (other microservices, third-party APIs, message brokers) are stubbed. This catches the ten most expensive production bug classes — from @Transactional proxy bypass to ObjectMapper serialization mismatches — that unit tests structurally cannot detect.

The Problem with @MockBean​

When you use @MockBean, you are writing a script of what you think your code does, not what it actually does.

// Traditional Mockito - Fragile & Manual
when(repository.findById("123")).thenReturn(Optional.of(user));

If the database schema changes, this test passes, but production fails. This is the Reality Gap.

The BitDive Approach: Record & Replay​

Instead of manually writing mocks, BitDive records the actual behavior of your Spring Boot application in production or staging.

  1. Record: The BitDive Agent captures the input (HTTP Request) and all downstream interactions (SQL, Kafka, External APIs).
  2. Virtualize: BitDive creates a "Virtualization Layer" around your Service.
  3. Replay: The test runs your actual Service logic, but "replays" the recorded data for dependencies.

Key Benefits for Spring Developers​

1. No Boilerplate​

Stop writing hundreds of lines of when(...).thenReturn(...). BitDive creates the test context automatically.

2. Fast Context Startup​

BitDive can start a "slice" of your Spring Context in milliseconds, bypassing the heavy overhead of full @SpringBootTest.

3. Production Realism​

Your tests are based on real data, not synthetic data. This catches edge cases (nulls, weird encodings) that manual mocks miss.

Example: Testing a UserService​

class UserServiceReplayTest extends ReplayTestBase {

@Override
protected List<ReplayTestConfiguration> getTestConfigurations() {
// Load the recording of the "User Creation" scenario
return ReplayTestUtils.fromRestApiWithJsonContentConfigFile(
Arrays.asList("user-creation-flow-id")
);
}

// No manual test method needed - the harness runs the replay automatically
}

Spring Slice Tests and BitDive​

Spring Boot provides slice test annotations that start only a targeted piece of the context:

AnnotationWhat It TestsLoads
@WebMvcTestControllers, filters, serializationWeb layer only
@DataJpaTestRepositories, JPA mappings, queriesJPA + in-memory DB
@JsonTestJSON serialization/deserializationJackson ObjectMapper

These slices are faster than full @SpringBootTest and useful for verifying specific seams. However, they have a key limitation: the test data is still synthetic. You write the test inputs manually, which means you are testing what you think happens, not what actually happens.

BitDive's Deterministic Replay complements slices by providing production-grounded data. Instead of manually creating a UserEntity for your @DataJpaTest, BitDive replays the exact ResultSet that your production database returned. This combines the speed of a slice with the realism of production data.

Next Steps​