<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitry Turmyshev</title>
    <description>The latest articles on DEV Community by Dmitry Turmyshev (@dmitry_turmyshev).</description>
    <link>https://dev.to/dmitry_turmyshev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2276381%2F94cf9a49-9d1c-431b-8723-3fe6fe103336.jpg</url>
      <title>DEV Community: Dmitry Turmyshev</title>
      <link>https://dev.to/dmitry_turmyshev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmitry_turmyshev"/>
    <language>en</language>
    <item>
      <title>Stop Cluttering Your Codebase with Brittle Generated Tests</title>
      <dc:creator>Dmitry Turmyshev</dc:creator>
      <pubDate>Tue, 07 Apr 2026 16:45:08 +0000</pubDate>
      <link>https://dev.to/dmitry_turmyshev/stop-cluttering-your-codebase-with-brittle-generated-tests-ehc</link>
      <guid>https://dev.to/dmitry_turmyshev/stop-cluttering-your-codebase-with-brittle-generated-tests-ehc</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; In the industry, there is a weird habit: if a tool can generate tests, it is considered automatically useful. If you have 300 new &lt;code&gt;.java&lt;/code&gt; files in your repo after recording a scenario, the team assumes they have "more quality." They are wrong. Automated test generation often turns into a source of engineering pain, cluttering repositories and burying real regressions in noise. There is a more mature path: capture real execution traces, store them as data, and replay them dynamically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hidden Cost of Generated Test Code
&lt;/h2&gt;

&lt;p&gt;The problem is not that tests are created automatically. The problem is &lt;strong&gt;what&lt;/strong&gt; exactly is created.&lt;/p&gt;

&lt;p&gt;If an instrument produces static &lt;code&gt;.java&lt;/code&gt; files that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fail because of a timestamp change&lt;/li&gt;
&lt;li&gt;Fail due to an extra field in a JSON response&lt;/li&gt;
&lt;li&gt;Fail because of a shift in JSON field order&lt;/li&gt;
&lt;li&gt;Fail after an internal method rename&lt;/li&gt;
&lt;li&gt;Fail after any refactoring that doesn't change business logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...then it is not a regression testing strategy. It is just a generator of fragile noise.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fragility Cascade
&lt;/h3&gt;

&lt;p&gt;When your repository becomes a dumping ground for side artifacts that no one wrote and no one wants to read, your engineering velocity dies.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1: The cascade of fragility when tests are treated as code artifacts. Dev.to text version because Mermaid does not render there as a diagram.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxs9usmy3z2cagsmazpo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxs9usmy3z2cagsmazpo.png" alt="Flowchart illustrating the 'Cascade of Fragility' where auto-deriving logic leads to massive generated test files, causing noisy PRs, fragile CI failures, and team fear of change." width="800" height="916"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Existing codebase:&lt;/strong&gt; You have your application's source code and logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-derive logic:&lt;/strong&gt; A tool or AI agent parses code structure or record local execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate 100s of .java files:&lt;/strong&gt; The system produces massive amounts of boilerplate code (mocks, setup, assertions) to "freeze" the state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit to repository:&lt;/strong&gt; Pull requests drown in garbage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Noisy PRs:&lt;/strong&gt; Every minor change triggers a avalanche of test updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragile CI failures:&lt;/strong&gt; CI turns red for technical fluctuations, not business bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team fears change:&lt;/strong&gt; Refactoring is avoided because the test maintenance is too expensive.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why Generated Tests Break "Every Sneeze"
&lt;/h2&gt;

&lt;p&gt;Generated tests fixate on the wrong things. Instead of verifying business invariants, key results, or significant contracts, they verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic UUIDs&lt;/li&gt;
&lt;li&gt;Timestamps&lt;/li&gt;
&lt;li&gt;Technical headers&lt;/li&gt;
&lt;li&gt;Serialized form (field order)&lt;/li&gt;
&lt;li&gt;Service hostnames&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The "Bad Path" Example
&lt;/h3&gt;

&lt;p&gt;Here is a typical anti-pattern: a statically generated test that looks "powerful" but is actually a brittle trap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;shouldReplayCreateContract_2026_03_19_15_42_11&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ContractRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContractRequest&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setClientId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"12345"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setProductCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"IPOTEKA"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Brittle timestamp!&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRequestedAt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OffsetDateTime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2026-03-19T15:42:11.123+03:00"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="nc"&gt;ContractResponse&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contractService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createContract&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OK"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// Brittle UUID!&lt;/span&gt;
    &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c7d89e8e-5d7f-4f7a-a2a2-873638f47f44"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRequestId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2026-03-19T15:42:11.456+03:00"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCreatedAt&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="c1"&gt;// Brittle JSON structure comparison!&lt;/span&gt;
    &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
        {
          "status":"OK",
          "requestId":"c7d89e8e-5d7f-4f7a-a2a2-873638f47f44",
          "createdAt":"2026-03-19T15:42:11.456+03:00",
          "technicalInfo":{
            "host":"node-17",
            "thread":"http-nio-8080-exec-5"
          }
        }
        """&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test catches every technical shiver but misses the signal. The smallest DTO refactoring makes this test red without any business logic failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The False Alarm Trap
&lt;/h2&gt;

&lt;p&gt;This structural coupling trains developers to ignore the CI.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2: The signal-to-noise ratio problem in automated test generation. Dev.to text version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0e4hrlmxas4om4550lfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0e4hrlmxas4om4550lfg.png" alt="Diagram showing the signal-to-noise ratio problem in automated test generation, where false alarms from minor refactors lead to teams ignoring CI signals and missing real regressions." width="800" height="681"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you refactor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Did logic change? No.&lt;/strong&gt; Generated tests fail anyway. This is a &lt;strong&gt;false alarm&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did logic change? Yes.&lt;/strong&gt; There is a real bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But because the developer already sees 30+ failures from the false alarms, the real regression is drowned in the noise. The team ends up "fixing" tests by bulk-updating mocks without checking the logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  BitDive: A Replay Platform, Not a Code Generator
&lt;/h2&gt;

&lt;p&gt;BitDive offers a more mature model. We don't flood your project with static test files. Instead, we treat scenarios as &lt;strong&gt;data&lt;/strong&gt; and use a centralized &lt;strong&gt;replay engine&lt;/strong&gt; to verify behavior.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3: The clean BitDive verified scenario flow. Dev.to text version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwr9o4bivukufrt441at7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwr9o4bivukufrt441at7.png" alt="Flowchart of the BitDive verified scenario flow: capturing real behavior as data and replaying it through a dynamic engine to keep the repository clean and ensure real behavior is verified." width="800" height="914"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture: Tests as Data
&lt;/h3&gt;

&lt;p&gt;The core shift is simple: stop committing test code. Commit the &lt;strong&gt;test scenario&lt;/strong&gt; as a data snapshot.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 4: BitDive architecture - separating capture (data) from replay (verification). Dev.to text version.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8qd9wqd45622hpqjn3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8qd9wqd45622hpqjn3x.png" alt="Architectural diagram of BitDive separation between Capture (Recording Phase) and Replay (Test Runtime Environment), illustrating how traces and snapshots are used to drive tests without code generation." width="800" height="847"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation: The "Good Path"
&lt;/h3&gt;

&lt;p&gt;In your repository, you keep one clean runner that loads all scenarios dynamically using JUnit 5 &lt;code&gt;DynamicNode&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.DynamicNode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.DynamicTest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.TestFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BitDiveReplayTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ReplayTestBase&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@TestFactory&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DynamicNode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;replayRecordedScenarios&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;traceRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadAll&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;DynamicTest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dynamicTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;testDisplayName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
                        &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                            &lt;span class="nc"&gt;ReplayResult&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;replayEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replay&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                            &lt;span class="n"&gt;replayAssertions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;assertMatches&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expectedSnapshot&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                        &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't clutter your &lt;code&gt;src/test/java&lt;/code&gt;. Adding new scenarios just means adding new trace data files to your resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the Approaches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Generated .java Tests&lt;/th&gt;
&lt;th&gt;BitDive Trace Replay&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repository Impact&lt;/td&gt;
&lt;td&gt;Massive (1000s of files)&lt;/td&gt;
&lt;td&gt;Minimal (Data files + 1 runner)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;High (breaks on refactoring)&lt;/td&gt;
&lt;td&gt;Low (centralized normalization)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review Effort&lt;/td&gt;
&lt;td&gt;Exhausting noisy PRs&lt;/td&gt;
&lt;td&gt;Meaningful logic changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust in CI&lt;/td&gt;
&lt;td&gt;Low (false positives hide bugs)&lt;/td&gt;
&lt;td&gt;High (contract-level verification)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Linear growth of boilerplace&lt;/td&gt;
&lt;td&gt;Logarithmic growth of data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why Replay Wins at Scale
&lt;/h2&gt;

&lt;p&gt;Traditional generated tests have a "stupid" growth model: &lt;strong&gt;more scenarios = more files&lt;/strong&gt;.&lt;br&gt;
More files lead to heavier reviews, which leads to lower trust and "formal" approvals.&lt;/p&gt;

&lt;p&gt;BitDive's replay approach scales differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More scenarios&lt;/strong&gt; = more trace snapshots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay engine&lt;/strong&gt; remains the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalization rules&lt;/strong&gt; are centralized (e.g., ignore all UUIDs in one place).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale is handled by data&lt;/strong&gt;, not code maintenance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stop the Code Clutter
&lt;/h2&gt;

&lt;p&gt;BitDive captures real behavior and replays it as deterministic tests. No generated garbage. No fragile mocks. Just verified behavior that stays green through refactoring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bitdive.io/" rel="noopener noreferrer"&gt;BitDive.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>testing</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>5 Jackson Configuration Changes That Silently Break Your Microservices</title>
      <dc:creator>Dmitry Turmyshev</dc:creator>
      <pubDate>Wed, 04 Mar 2026 23:00:53 +0000</pubDate>
      <link>https://dev.to/dmitry_turmyshev/5-jackson-configuration-changes-that-silently-break-your-microservices-294p</link>
      <guid>https://dev.to/dmitry_turmyshev/5-jackson-configuration-changes-that-silently-break-your-microservices-294p</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Your service compiles. Your unit tests pass. Your integration tests are green. But a single line in your &lt;code&gt;ObjectMapper&lt;/code&gt; configuration just changed what every outgoing HTTP request looks like. The downstream service cannot parse the payload anymore, and you will find out in production. Here are five Jackson configuration changes that cause this, with exact before/after JSON for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Jackson Is the Most Dangerous Dependency in Your Stack
&lt;/h2&gt;

&lt;p&gt;Jackson is everywhere. If you run Spring Boot, Jackson serializes every &lt;code&gt;@RestController&lt;/code&gt; response, every Feign client request, every Kafka message with a JSON payload, and every Redis value stored as JSON.&lt;/p&gt;

&lt;p&gt;This makes &lt;code&gt;ObjectMapper&lt;/code&gt; configuration one of the most impactful settings in a microservice. A single property change can alter the serialized output of every outgoing HTTP call in the service. And because the change happens at the serialization layer, it is invisible to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt; that mock the HTTP client (never see the serialized bytes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract tests&lt;/strong&gt; that check field presence but not exact format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The compiler&lt;/strong&gt; (the Java objects are unchanged)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The developer&lt;/strong&gt; (the diff shows a one-line config change, not a payload break)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: the most common cause of "it worked yesterday" in microservices is not a logic bug. It is a serialization change.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;WRITE_DATES_AS_TIMESTAMPS&lt;/code&gt; Turns ISO Strings Into Numbers
&lt;/h2&gt;

&lt;p&gt;This is the single most common Jackson-related production incident in Spring Boot applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The config change:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;SerializationFeature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WRITE_DATES_AS_TIMESTAMPS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or equivalently in &lt;code&gt;application.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;jackson&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;serialization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;write-dates-as-timestamps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-28T14:30:00.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expiresAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-28T14:30:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1772316600000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expiresAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1774908600000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downstream service expects an ISO-8601 string. It receives a Unix timestamp as a number. &lt;code&gt;DateTimeParseException&lt;/code&gt; in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why tests miss it:&lt;/strong&gt; Unit tests that mock the HTTP client never see the serialized JSON. They work with Java &lt;code&gt;Instant&lt;/code&gt; or &lt;code&gt;LocalDateTime&lt;/code&gt; objects, which are unchanged. The contract test checks that &lt;code&gt;createdAt&lt;/code&gt; exists and is not null. It does not check the format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How common is this?&lt;/strong&gt; Extremely. Spring Boot's default depends on whether &lt;code&gt;JavaTimeModule&lt;/code&gt; is registered and which version of &lt;code&gt;jackson-datatype-jsr310&lt;/code&gt; is on the classpath. A Spring Boot minor version bump can flip this behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;Include.NON_NULL&lt;/code&gt; Makes Fields Disappear
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The config change:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSerializationInclusion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonInclude&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Include&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NON_NULL&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt; (field present with &lt;code&gt;null&lt;/code&gt; value):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"loyaltyTier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GOLD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"referralCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (field completely absent):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"loyaltyTier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GOLD"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downstream service processes the payload. When &lt;code&gt;referralCode&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt;, it clears the existing referral. When &lt;code&gt;referralCode&lt;/code&gt; is absent, it keeps the old value. These are different business outcomes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why tests miss it:&lt;/strong&gt; The Java object has &lt;code&gt;referralCode = null&lt;/code&gt; in both cases. Any test that asserts on the Java object sees the same result. Only the serialized JSON is different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trap:&lt;/strong&gt; This is often introduced as a "clean up" or "reduce payload size" optimization. The pull request looks harmless: one line, no logic change, no test failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Enum Serialization Strategy Changes Strings to Integers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The config change:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;SerializationFeature&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WRITE_ENUMS_USING_INDEX&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or by adding &lt;code&gt;@JsonValue&lt;/code&gt; on an enum method, or switching from &lt;code&gt;@JsonFormat(shape = STRING)&lt;/code&gt; to default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PROCESSING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ORD-789"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downstream service does &lt;code&gt;OrderStatus.valueOf(jsonNode.get("status").asText())&lt;/code&gt;. It receives &lt;code&gt;"1"&lt;/code&gt; instead of &lt;code&gt;"PROCESSING"&lt;/code&gt;. &lt;code&gt;IllegalArgumentException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The variant:&lt;/strong&gt; Even without &lt;code&gt;WRITE_ENUMS_USING_INDEX&lt;/code&gt;, reordering enum constants changes ordinal values. If any downstream service stores or compares enums by ordinal, the behavior silently changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why tests miss it:&lt;/strong&gt; The Java enum is still &lt;code&gt;OrderStatus.PROCESSING&lt;/code&gt;. No logic changed. The test asserts &lt;code&gt;assertEquals(OrderStatus.PROCESSING, order.getStatus())&lt;/code&gt; and it passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;code&gt;BigDecimal&lt;/code&gt; Serializes as Number Instead of String
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The config change:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Removing &lt;code&gt;@JsonFormat(shape = JsonFormat.Shape.STRING)&lt;/code&gt; from a DTO field, or changing &lt;code&gt;ObjectMapper&lt;/code&gt; to not use &lt;code&gt;BigDecimalAsStringSerializer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Removed from DTO:&lt;/span&gt;
&lt;span class="c1"&gt;// @JsonFormat(shape = JsonFormat.Shape.STRING)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TX-456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"149.99"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TX-456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;149.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks harmless. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript (and many JSON parsers) handle large numbers with floating-point precision loss: &lt;code&gt;0.1 + 0.2 !== 0.3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Financial systems that parse &lt;code&gt;amount&lt;/code&gt; as a string and pass it to &lt;code&gt;BigDecimal(String)&lt;/code&gt; now receive a JSON number&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;149.9900000000000002&lt;/code&gt; is a real production bug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why tests miss it:&lt;/strong&gt; &lt;code&gt;BigDecimal("149.99").equals(new BigDecimal(149.99))&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; in Java. But the unit test compares Java &lt;code&gt;BigDecimal&lt;/code&gt; objects, not the JSON wire format. The test passes. The downstream payment service truncates cents.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Custom &lt;code&gt;ObjectMapper&lt;/code&gt; Bean Overrides Spring Boot Defaults
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The config change:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Defining a custom &lt;code&gt;ObjectMapper&lt;/code&gt; &lt;code&gt;@Bean&lt;/code&gt; that does not include all modules Spring Boot auto-registers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Bean&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ObjectMapper&lt;/span&gt; &lt;span class="nf"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ObjectMapper&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerModule&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JavaTimeModule&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSerializationInclusion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonInclude&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Include&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NON_EMPTY&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this breaks:&lt;/strong&gt; Spring Boot auto-configures &lt;code&gt;ObjectMapper&lt;/code&gt; with a specific set of modules, features, and customizers. When you define your own &lt;code&gt;@Bean&lt;/code&gt;, Spring Boot's auto-configuration backs off entirely. You lose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Jdk8Module&lt;/code&gt; (Optional handling)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ParameterNamesModule&lt;/code&gt; (constructor deserialization)&lt;/li&gt;
&lt;li&gt;Any &lt;code&gt;Jackson2ObjectMapperBuilderCustomizer&lt;/code&gt; beans from other libraries&lt;/li&gt;
&lt;li&gt;Default date format settings&lt;/li&gt;
&lt;li&gt;Default property naming strategy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Before&lt;/strong&gt; (Spring Boot auto-configured):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accountHolder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"optionalNickname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registeredAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15T10:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After&lt;/strong&gt; (custom bean without Jdk8Module):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accountHolder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Smith"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"optionalNickname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"present"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JS"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registeredAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15T10:00:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt;&lt;/code&gt; now serializes as an object with &lt;code&gt;present&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; fields instead of the unwrapped value. Every downstream service that reads &lt;code&gt;optionalNickname&lt;/code&gt; as a string breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why tests miss it:&lt;/strong&gt; If the test runs in a different profile or uses a test-specific &lt;code&gt;ObjectMapper&lt;/code&gt;, it does not exercise the production bean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern: Zero Logic Change, Total Payload Change
&lt;/h2&gt;

&lt;p&gt;All five cases share the same characteristics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No business logic changed.&lt;/strong&gt; The Java objects are identical before and after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The compiler is happy.&lt;/strong&gt; No type errors, no warnings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests pass.&lt;/strong&gt; They assert on Java objects, not serialized JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract tests pass.&lt;/strong&gt; They check field presence and types, not exact serialization format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The serialized HTTP payload is different.&lt;/strong&gt; The actual bytes sent over the wire changed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is why testing at the serialization boundary is critical in microservices. The gap between "the Java object is correct" and "the JSON over HTTP is correct" is where these regressions live.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Catch Serialization Regressions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Serialization-specific unit tests
&lt;/h3&gt;

&lt;p&gt;Write tests that serialize to JSON and assert on the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;customer_serialization_format_is_stable&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GOLD"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;assertThatJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"loyaltyTier"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GOLD"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"referralCode"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isPresent&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;isNull&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works but requires manual maintenance for every DTO. In a system with hundreds of DTOs, it does not scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Golden file tests
&lt;/h3&gt;

&lt;p&gt;Serialize objects and compare against committed &lt;code&gt;.json&lt;/code&gt; files. Any change to serialization requires an explicit update to the golden file. This is more scalable but still requires you to know which DTOs to test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Before/after trace comparison
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://bitdive.io" rel="noopener noreferrer"&gt;bitDive&lt;/a&gt; captures the actual serialized HTTP exchanges from your running application. Trigger the same API call before and after the configuration change. BitDive compares the real outgoing payloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Diff in POST /api/payments (request body):
  - "amount": "149.99"     → "amount": 149.99
  - "createdAt": "2026-..."  → "createdAt": 1772316600000
  + "referralCode" field removed (was null)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches all five regressions described above, because it operates on the actual wire format, not on Java objects. The comparison works on traces captured from real API calls, so it reflects the exact bytes your service sends over HTTP.&lt;/p&gt;

&lt;p&gt;If you don't want to find out about these breakages in production, check out bitDive. You can see exactly how before/after trace comparison catches these silent breakages in the &lt;a href="https://bitdive.io/docs/testing/api-verification/" rel="noopener noreferrer"&gt;Inter-Service API Verification Guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>devchallenge</category>
      <category>qa</category>
    </item>
    <item>
      <title>Your Service Passes All Tests But Breaks Production: Detecting Inter-Service API Regression</title>
      <dc:creator>Dmitry Turmyshev</dc:creator>
      <pubDate>Tue, 03 Mar 2026 10:44:22 +0000</pubDate>
      <link>https://dev.to/dmitry_turmyshev/your-service-passes-all-tests-but-breaks-production-detecting-inter-service-api-regression-1igk</link>
      <guid>https://dev.to/dmitry_turmyshev/your-service-passes-all-tests-but-breaks-production-detecting-inter-service-api-regression-1igk</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; The most dangerous bugs in microservices are not inside a service. They are between services. A code change can make a service pass all its local tests while silently altering what it sends to downstream APIs: different payload, missing header, changed error format. These regressions are invisible to unit tests, hard to catch with contract tests, and expensive in production. &lt;a href="https://bitdive.io" rel="noopener noreferrer"&gt;BitDive&lt;/a&gt; detects them by capturing real HTTP exchanges in execution traces and comparing them before and after a code change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug That All Tests Miss
&lt;/h2&gt;

&lt;p&gt;Here is a scenario every microservices team has experienced at least once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A developer updates a shared library or changes a DTO mapping.&lt;/li&gt;
&lt;li&gt;All unit tests pass. The integration test suite is green.&lt;/li&gt;
&lt;li&gt;The service deploys to production.&lt;/li&gt;
&lt;li&gt;Hours later: a downstream service starts throwing deserialization errors, or silently processing wrong data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The root cause: the service changed how it interacts with other services at the HTTP level. The actual bytes it sends over the wire are different. But no test was checking that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common triggers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jackson or ObjectMapper update changed date/enum/null serialization&lt;/li&gt;
&lt;li&gt;DTO field renamed or removed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Include.NON_NULL&lt;/code&gt; toggled on or off&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WRITE_DATES_AS_TIMESTAMPS&lt;/code&gt; enabled&lt;/li&gt;
&lt;li&gt;MapStruct/ModelMapper update swapped field mapping&lt;/li&gt;
&lt;li&gt;Spring Boot version bump changed default serialization&lt;/li&gt;
&lt;li&gt;Feign interceptor refactoring dropped an auth header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these touch business logic. All of them change the runtime API contract between services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Existing Tests Are Blind Here
&lt;/h2&gt;

&lt;p&gt;Each testing approach has a structural blind spot when it comes to inter-service API compatibility:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unit tests&lt;/strong&gt; mock the HTTP client entirely. They verify local logic but never see the actual serialized request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract tests (Pact)&lt;/strong&gt; verify predefined examples. If an example does not cover the specific field, serialization format, or header, the regression slips through. A Pact contract that checks &lt;code&gt;status&lt;/code&gt; exists does not catch &lt;code&gt;"ACTIVE"&lt;/code&gt; becoming &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenAPI validation&lt;/strong&gt; checks schema compliance but not runtime serialization behavior. The schema says &lt;code&gt;string&lt;/code&gt;, but Jackson now serializes the &lt;code&gt;LocalDate&lt;/code&gt; as a Unix timestamp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End-to-end tests&lt;/strong&gt; can catch the problem, but they are slow, flaky, and cover only a narrow set of scenarios.&lt;/p&gt;

&lt;p&gt;The gap: no standard testing approach compares the &lt;strong&gt;actual serialized HTTP exchange&lt;/strong&gt; before and after a code change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What BitDive Sees That Other Tests Cannot
&lt;/h2&gt;

&lt;p&gt;BitDive captures execution traces from running Java applications. Each trace includes &lt;strong&gt;every outgoing HTTP call&lt;/strong&gt; with full details:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What BitDive captures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Endpoint&lt;/td&gt;
&lt;td&gt;HTTP method, URL, path variables, query parameters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request headers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Authorization&lt;/code&gt;, &lt;code&gt;Content-Type&lt;/code&gt;, &lt;code&gt;X-Correlation-Id&lt;/code&gt;, tenant/feature headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body&lt;/td&gt;
&lt;td&gt;The actual serialized JSON as sent over the wire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response status&lt;/td&gt;
&lt;td&gt;HTTP status code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response body&lt;/td&gt;
&lt;td&gt;The actual response payload as received&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error details&lt;/td&gt;
&lt;td&gt;Exception class, message, stack trace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key distinction: this is the &lt;strong&gt;real runtime exchange&lt;/strong&gt;, not the Java object before serialization. If Jackson serializes a &lt;code&gt;BigDecimal&lt;/code&gt; as &lt;code&gt;"19.99"&lt;/code&gt; in the baseline but as &lt;code&gt;19.99&lt;/code&gt; after an ObjectMapper change, the difference is visible in the trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four Regressions That Traces Catch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Serialization Change After Jackson Upgrade
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before the change:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-28"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ACTIVE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"19.99"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After Jackson config update:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1740700800000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;19.99&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unit tests pass. Contract tests pass (they only check field presence). BitDive's before/after trace comparison flags three diffs: date format, enum serialization, number type.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Missing Auth Header After Interceptor Refactoring
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; Every outgoing request includes &lt;code&gt;Authorization: Bearer ...&lt;/code&gt; and &lt;code&gt;X-Correlation-Id: abc-123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; The interceptor was refactored. &lt;code&gt;X-Correlation-Id&lt;/code&gt; is still sent, but &lt;code&gt;Authorization&lt;/code&gt; is missing on one specific call path to the payment service.&lt;/p&gt;

&lt;p&gt;No test covers this header on this specific route. The payment service returns &lt;code&gt;401&lt;/code&gt;. BitDive shows the header diff immediately in the trace comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Changed Error Contract
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; Downstream returns &lt;code&gt;404&lt;/code&gt; with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CUSTOMER_NOT_FOUND"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customer 42 not found"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; Downstream returns &lt;code&gt;500&lt;/code&gt; with plain text: &lt;code&gt;Internal Server Error&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The upstream service's error handler expects a JSON body with a &lt;code&gt;code&lt;/code&gt; field. It now throws an &lt;code&gt;HttpMessageNotReadableException&lt;/code&gt;. BitDive detects both the status code change and the response body format change.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Silent Call Sequence Change
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; The service calls &lt;code&gt;GET /accounts/42&lt;/code&gt;, then &lt;code&gt;POST /transactions&lt;/code&gt; with account data from the response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After refactoring:&lt;/strong&gt; The service calls &lt;code&gt;POST /transactions&lt;/code&gt; directly, without fetching account data first. It sends a hardcoded default instead of the real account segment.&lt;/p&gt;

&lt;p&gt;Contract tests for each endpoint pass individually. The business scenario is broken. BitDive detects that one call disappeared and the remaining call sends different payload data.&lt;/p&gt;

&lt;h2&gt;
  
  
  How BitDive Detects These Regressions
&lt;/h2&gt;

&lt;p&gt;BitDive uses &lt;a href="https://bitdive.io/docs/testing/api-verification/" rel="noopener noreferrer"&gt;before/after trace comparison&lt;/a&gt; to catch API regressions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture a baseline trace.&lt;/strong&gt; Trigger the business scenario via a real API call (curl, Postman, your frontend). BitDive's agent captures a trace with all outgoing HTTP exchanges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make a code change&lt;/strong&gt; (refactor, library upgrade, DTO update, Jackson config change).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger the same scenario again.&lt;/strong&gt; Call the same endpoint on the updated service. BitDive captures a new trace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare the two traces&lt;/strong&gt; across every layer: endpoint diff, header diff, request body diff, response diff, status diff, call sequence diff.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because traces are captured from real API calls, the comparison operates on actual serialized HTTP exchanges, not on mocked data or theoretical schemas. If the code now sends a different payload, drops a header, or changes the call sequence, the diff shows exactly what changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Every Diff Is a Bug
&lt;/h2&gt;

&lt;p&gt;A useful system must distinguish noise from breakage. BitDive classifies differences:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected (not a regression):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New optional field in the response&lt;/li&gt;
&lt;li&gt;New trace or correlation header&lt;/li&gt;
&lt;li&gt;Different timestamps, UUIDs, request IDs (automatically excluded)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Critical (likely a regression):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required field disappeared from request body&lt;/li&gt;
&lt;li&gt;Field type changed (string to number, date format changed)&lt;/li&gt;
&lt;li&gt;Auth header missing&lt;/li&gt;
&lt;li&gt;Error status code changed (404 became 500)&lt;/li&gt;
&lt;li&gt;Call removed or new unexpected call appeared&lt;/li&gt;
&lt;li&gt;Response body structure changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fields like &lt;code&gt;requestId&lt;/code&gt;, &lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;traceId&lt;/code&gt;, and &lt;code&gt;UUID&lt;/code&gt; are automatically masked. Custom masking and comparison policies can be configured in the &lt;a href="https://bitdive.io/docs/configuration/" rel="noopener noreferrer"&gt;Configuration Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Matters Most
&lt;/h2&gt;

&lt;p&gt;Inter-service API regression control delivers the most value in systems with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many internal REST APIs between microservices&lt;/li&gt;
&lt;li&gt;Frequent library and framework upgrades&lt;/li&gt;
&lt;li&gt;Strong reliance on DTOs and internal contracts&lt;/li&gt;
&lt;li&gt;Active refactoring of integration layers&lt;/li&gt;
&lt;li&gt;AI-assisted development (where agents may change serialization behavior without understanding downstream impact)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these environments, the most expensive production bugs are not "the code does not compile." They are "the services no longer interact the same way."&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How is this different from Pact contract testing?
&lt;/h3&gt;

&lt;p&gt;Pact verifies that a service conforms to predefined contract examples. BitDive verifies that actual runtime API behavior remained the same after a code change. They complement each other. Pact catches explicit contract violations. BitDive catches implicit changes that fall outside the contract scope: serialization drift, header changes, error body mutations. See the &lt;a href="https://bitdive.io/docs/comparisons/bitdive-vs-contract-testing" rel="noopener noreferrer"&gt;full comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to write separate tests for API regression?
&lt;/h3&gt;

&lt;p&gt;No. BitDive compares traces captured from real API calls. Trigger the same business scenario before and after a code change, and BitDive diffs the outgoing HTTP exchanges automatically. No test code needed for this verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about performance overhead?
&lt;/h3&gt;

&lt;p&gt;BitDive captures traces using a standard Java Agent with 0.5–5% overhead. Trace comparison happens at test time, not at runtime, so there is no production performance impact beyond the capture phase.&lt;/p&gt;

</description>
      <category>java</category>
      <category>microservices</category>
      <category>testing</category>
      <category>springboot</category>
    </item>
    <item>
      <title>Spring Boot Integration Testing: Full Context, Stubbed Boundaries, Zero Flakiness</title>
      <dc:creator>Dmitry Turmyshev</dc:creator>
      <pubDate>Thu, 26 Feb 2026 19:46:57 +0000</pubDate>
      <link>https://dev.to/dmitry_turmyshev/spring-boot-integration-testing-full-context-stubbed-boundaries-zero-flakiness-1kcn</link>
      <guid>https://dev.to/dmitry_turmyshev/spring-boot-integration-testing-full-context-stubbed-boundaries-zero-flakiness-1kcn</guid>
      <description>&lt;h2&gt;
  
  
  What "Integration Test" Means Here
&lt;/h2&gt;

&lt;p&gt;The term "integration test" is overloaded. In some teams it means two microservices talking over a network. In others it means a full E2E suite running against staging. In this article, it means something specific:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One Spring Boot service, tested as a system within its own boundaries.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The full Spring context boots. All internal beans, validators, mappers, aspects, security filters, and transaction proxies are real. The test enters through the actual HTTP endpoint (not a direct service call) and exits through a real database write. The only things that are stubbed are dependencies that live &lt;em&gt;outside&lt;/em&gt; the service: calls to other microservices, external APIs, outbound message queues.&lt;/p&gt;

&lt;p&gt;This matches how &lt;a href="https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html" rel="noopener noreferrer"&gt;Spring's own documentation&lt;/a&gt; defines integration testing: any test that loads an &lt;code&gt;ApplicationContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06rkyxptdyorfxpqiemh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06rkyxptdyorfxpqiemh.png" alt="Spring Boot Integration Testing: Full Context with Stubbed Boundaries" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Inside vs Outside the Service Boundary
&lt;/h2&gt;

&lt;p&gt;The boundary is simple: everything that belongs to your deployable unit is &lt;em&gt;internal&lt;/em&gt;. Everything that requires a network call to another team's system is &lt;em&gt;external&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal (real in the test):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The entire Spring context: all beans, all auto-configuration&lt;/li&gt;
&lt;li&gt;Business logic: services, domain rules, calculations&lt;/li&gt;
&lt;li&gt;Data access: repositories, JPA mappings, SQL queries&lt;/li&gt;
&lt;li&gt;Infrastructure: &lt;code&gt;@Transactional&lt;/code&gt; boundaries, &lt;code&gt;@Validated&lt;/code&gt;, &lt;code&gt;@Cacheable&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;HTTP layer: controllers, filters, exception handlers, serialization&lt;/li&gt;
&lt;li&gt;Adapters: mappers, converters, aspects, event listeners&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;External (stubbed in the test):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feign clients or WebClient calls to other microservices&lt;/li&gt;
&lt;li&gt;REST calls to third-party APIs (Stripe, CRM, payment gateways)&lt;/li&gt;
&lt;li&gt;Outbound Kafka/RabbitMQ messages (when the test focuses on correct payload formation, not delivery)&lt;/li&gt;
&lt;li&gt;Any dependency that introduces network latency, rate limits, or data you don't control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle: we do not "cut" the internal chain. We only replace the responses of the outside world so the entire logic inside the service runs end-to-end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Full Spring Context Matters
&lt;/h2&gt;

&lt;p&gt;You can have 100% unit test coverage and still hit production with these bugs. Every one of them lives at the seams that unit tests mock away:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ObjectMapper&lt;/code&gt; misconfiguration.&lt;/strong&gt; Dates serialize as timestamps instead of ISO strings. Enums serialize as ordinals. Null handling policy silently changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation not applied.&lt;/strong&gt; &lt;code&gt;@Validated&lt;/code&gt; is missing on the controller parameter, or the wrong &lt;code&gt;Validator&lt;/code&gt; bean is active.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction proxy bypass.&lt;/strong&gt; A &lt;code&gt;@Transactional&lt;/code&gt; method is called from within the same bean. The proxy never intercepts it. The transaction never opens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository query vs real schema.&lt;/strong&gt; A JPQL query is syntactically valid but fails against the actual column types or naming conventions in the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DTO mapping breaks.&lt;/strong&gt; A field rename in the request DTO silently breaks the JSON contract. Jackson ignores the unknown field by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security filters block the request.&lt;/strong&gt; A Spring Security rule change starts rejecting requests that used to pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aspect side effects.&lt;/strong&gt; A logging or metrics aspect modifies behavior, swallows exceptions, or changes the execution order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External client configuration.&lt;/strong&gt; &lt;code&gt;RestTemplate&lt;/code&gt; or &lt;code&gt;WebClient&lt;/code&gt; sends the wrong headers, wrong timeouts, or the wrong base URL from &lt;code&gt;application.yml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response serialization mismatch.&lt;/strong&gt; The HTTP response body differs from the contract the frontend expects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unit tests stay green through all of these because they mock the infrastructure where these bugs live. Integration tests exercise the real infrastructure. That is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Stub External Dependencies in Spring Boot
&lt;/h2&gt;

&lt;p&gt;External dependencies enter your code in one of three forms. Each has a clean stubbing pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Feign Client or a Java Interface Bean
&lt;/h3&gt;

&lt;p&gt;If you have a &lt;code&gt;CrmClient&lt;/code&gt; or &lt;code&gt;PaymentGateway&lt;/code&gt; interface injected as a Spring bean, use &lt;code&gt;@MockBean&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@MockBean&lt;/span&gt;
&lt;span class="nc"&gt;CrmClient&lt;/span&gt; &lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spring boots the full context but replaces this one bean with a Mockito mock. Every other bean is real. This is the fastest and most common approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 2: RestTemplate or WebClient with a Base URL
&lt;/h3&gt;

&lt;p&gt;If your service calls an external API through &lt;code&gt;RestTemplate&lt;/code&gt; or &lt;code&gt;WebClient&lt;/code&gt;, you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Replace the bean&lt;/strong&gt; with &lt;code&gt;@MockBean&lt;/code&gt; (same as above).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Point the base URL&lt;/strong&gt; to a local WireMock or MockWebServer instance. This tests the real HTTP serialization, headers, and error handling against a controlled stub.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enqueue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockResponse&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setResponseCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{\\"&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;":\\"&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;",\\"&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;":\\"&lt;/span&gt;&lt;span class="no"&gt;OK&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option 2 is more realistic because it exercises the actual HTTP stack. Use it when serialization or error handling is part of the risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 3: Outbound Message Queues
&lt;/h3&gt;

&lt;p&gt;For Kafka or RabbitMQ producers, the test usually verifies that the correct event payload was formed, not that it was delivered. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@MockBean&lt;/code&gt; the producer and verify the call with &lt;code&gt;verify(producer).send(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use an embedded broker via Testcontainers if the consumer logic is part of the critical chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The rule in all three patterns:&lt;/strong&gt; stub only what crosses the service boundary. Never mock internal services or repositories for convenience. That defeats the purpose of running the full chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Example: Testing a Policy Signing Flow
&lt;/h2&gt;

&lt;p&gt;A realistic microservice scenario: &lt;code&gt;POST /api/policy/sign&lt;/code&gt; triggers validation, domain logic, a database write, and a call to an external CRM service for customer data. The CRM is the only external dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Service Code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;CrmClient&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;CustomerDto&lt;/span&gt; &lt;span class="nf"&gt;getCustomer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PolicyService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;CrmClient&lt;/span&gt; &lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;PolicyRepository&lt;/span&gt; &lt;span class="n"&gt;policyRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PolicyService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CrmClient&lt;/span&gt; &lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                         &lt;span class="nc"&gt;PolicyRepository&lt;/span&gt; &lt;span class="n"&gt;policyRepository&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crmClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;policyRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policyRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SignResponse&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SignRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;CustomerDto&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCustomer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;PolicyEntity&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PolicyEntity&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContractId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contractId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCustomerSegment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SIGNED"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;policyRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SignResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SIGNED"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/policy"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PolicyController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;PolicyService&lt;/span&gt; &lt;span class="n"&gt;policyService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;PolicyController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PolicyService&lt;/span&gt; &lt;span class="n"&gt;policyService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;policyService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policyService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/sign"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SignResponse&lt;/span&gt; &lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;SignRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;policyService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sign&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Integration Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webEnvironment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WebEnvironment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RANDOM_PORT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PolicyIntegrationTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="nc"&gt;TestRestTemplate&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="nc"&gt;PolicyRepository&lt;/span&gt; &lt;span class="n"&gt;policyRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@MockBean&lt;/span&gt;
    &lt;span class="nc"&gt;CrmClient&lt;/span&gt; &lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sign_happyPath_runsFullChainAndWritesToDb&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCustomer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomerDto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"VIP"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SignResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;postForEntity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"/api/policy/sign"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SignRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C-123"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;SignResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatusCode&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBody&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isNotNull&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBody&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SIGNED"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;PolicyEntity&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;policyRepository&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByContractId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C-123"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SIGNED"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;assertThat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCustomerSegment&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;isEqualTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"VIP"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crmClient&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getCustomer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test simultaneously verifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Spring context boots and all beans wire correctly.&lt;/li&gt;
&lt;li&gt;The HTTP endpoint deserializes the request.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;CrmClient&lt;/code&gt; is called with the right argument.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;@Transactional&lt;/code&gt; boundary works (the repository write commits).&lt;/li&gt;
&lt;li&gt;The domain logic maps the CRM segment to the policy entity.&lt;/li&gt;
&lt;li&gt;The response serializes correctly.&lt;/li&gt;
&lt;li&gt;The database contains the expected row after the transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any link in this chain breaks, the test fails. A unit test mocking &lt;code&gt;PolicyRepository&lt;/code&gt; and &lt;code&gt;CrmClient&lt;/code&gt; would stay green through most of these failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  How BitDive Automates This
&lt;/h2&gt;

&lt;p&gt;The hardest part of integration testing at scale is not writing &lt;code&gt;@SpringBootTest&lt;/code&gt;. It is maintaining hundreds of mock setups and fixture files as the service evolves.&lt;/p&gt;

&lt;p&gt;BitDive solves this by recording real execution traces from your running application, then replaying them as deterministic test scenarios. Instead of manually writing &lt;code&gt;when(...).thenReturn(...)&lt;/code&gt; for every external dependency, BitDive captures the actual responses that your service received in production or staging.&lt;/p&gt;

&lt;p&gt;The test harness is scenario-driven. You add a new test case by providing a scenario ID, not by writing Java code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PolicyControllerReplayTest&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ReplayTestBase&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReplayTestConfiguration&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getTestConfigurations&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ReplayTestUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromRestApiWithJsonContentConfigFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0d46c175-4926-4fb6-ad2f-866acdc72996"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BitDive handles the rest: booting the Spring context, intercepting external boundaries, replaying recorded responses, and stabilizing non-deterministic values (timestamps, UUIDs, random numbers).&lt;/p&gt;

&lt;p&gt;The result: integration tests built from real runtime data, not hand-crafted fixtures. Tests that work on the first run because they contain actual captured behavior. No hallucinated assertions. No mock maintenance burden.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Testing from Real Traces&lt;/strong&gt;&lt;br&gt;
BitDive records execution traces from Spring Boot applications and turns them into deterministic JUnit tests. No manual mock setup. Tests run with standard &lt;code&gt;mvn test&lt;/code&gt; in any CI environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When Unit Tests Are Enough (and When They Are Not)
&lt;/h2&gt;

&lt;p&gt;Use unit tests for pure logic that has no infrastructure ties: calculations, validations, mappings, state machines, complex transformations. If a method can run without Spring, a database, or HTTP, a unit test is the right tool.&lt;/p&gt;

&lt;p&gt;Use integration tests when the risk lives at the seams: serialization, configuration binding, transaction management, security, database queries, and the interaction between multiple beans that Spring wires together.&lt;/p&gt;

&lt;p&gt;The two levels reinforce each other. &lt;a href="https://bitdive.io/java-unit-tests/" rel="noopener noreferrer"&gt;Unit tests&lt;/a&gt; reduce the number of potential root causes when an integration test fails. &lt;a href="https://bitdive.io/java-integration-tests/" rel="noopener noreferrer"&gt;Integration tests&lt;/a&gt; guarantee that the real assembled application does not break at the seams where unit tests are structurally blind.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>qa</category>
      <category>testing</category>
    </item>
    <item>
      <title>Unit vs Component Tests in Spring: Where the Boundary Lies and Why You Need Both</title>
      <dc:creator>Dmitry Turmyshev</dc:creator>
      <pubDate>Wed, 18 Feb 2026 09:37:26 +0000</pubDate>
      <link>https://dev.to/dmitry_turmyshev/unit-vs-component-tests-in-spring-where-the-boundary-lies-and-why-you-need-both-3g4p</link>
      <guid>https://dev.to/dmitry_turmyshev/unit-vs-component-tests-in-spring-where-the-boundary-lies-and-why-you-need-both-3g4p</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; In real-world Spring projects, the "unit vs integration" debate almost always stems from the fact that "integration testing" has become a catch-all term for everything from &lt;code&gt;@SpringBootTest&lt;/code&gt; with Testcontainers to full-blown E2E runs on staging environments. To stop arguing and start shipping, we need to draw a clear line in the sand regarding responsibility.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://bitdive.io/java-unit-tests/" rel="noopener noreferrer"&gt;unit test&lt;/a&gt; answers one question: &lt;em&gt;"Is the logic correct in total isolation?"&lt;/em&gt; It deliberately cuts infrastructure out of the equation. &lt;/p&gt;

&lt;p&gt;A &lt;a href="https://bitdive.io/java-component-tests/" rel="noopener noreferrer"&gt;component test&lt;/a&gt; answers another: &lt;em&gt;"Does the component work as a system within its own boundaries, including its Spring wiring, configurations, serialization, transactions, and data access?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you only have units, you'll inevitably get burned at the seams. If you only have component tests, you'll pay with execution time, flakiness, and painful debugging. The winning strategy is simple: &lt;strong&gt;unit tests provide the speed and density of logic verification; component tests provide the confidence that the "real assembly" actually works.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjl2gk7cpqzy51geexqj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjl2gk7cpqzy51geexqj9.png" alt="Unit Tests vs Component Tests in Spring Boot - Testing Strategy Diagram" width="640" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Counts as a Unit Test in the Spring World (and Why It Should Be "Spring-Free")
&lt;/h2&gt;

&lt;p&gt;In a healthy engineering culture, a unit test &lt;strong&gt;never starts the Spring context&lt;/strong&gt;. Once you spin up a container, you’re introducing variables that have nothing to do with your code’s logic: auto-configuration, profiles, property binding, proxies, and bean initialization order. While these are critical to test, it shouldn't happen at the unit level.&lt;/p&gt;

&lt;p&gt;Imagine a service that applies discounts based on customer status and order composition. This code &lt;em&gt;must&lt;/em&gt; be verifiable without Spring, without a database, and without HTTP. This allows you to blast through dozens of edge cases in milliseconds and know exactly where an error lies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PricingServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_apply_discount_for_vip&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;PricingService&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PricingService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DiscountPolicy&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;Money&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;calculatePrice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;VIP&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Money&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_not_discount_for_regular&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;PricingService&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PricingService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DiscountPolicy&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="nc"&gt;Money&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;calculatePrice&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REGULAR&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Money&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal here is checking "pure" logic, not how Spring glues it together. If this test fails, the root cause is almost always localized to a single method. This is why the unit level is your ultimate tool for development velocity and logical certainty.&lt;/p&gt;

&lt;p&gt;If a dependency is complex, such as a repository or an external client, you substitute it. The trick isn't to "mock everything," but to mock exactly what is &lt;strong&gt;external&lt;/strong&gt; to the logic you're testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mockito&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Mockito&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RegistrationServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_refuse_when_email_exists&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;existsByEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a@b.com"&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;thenReturn&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;RegistrationService&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RegistrationService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assertThrows&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IllegalStateException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a@b.com"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"exists"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;existsByEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a@b.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;verifyNoMoreInteractions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This unit test does exactly what it’s supposed to: verify service behavior given specific responses from its dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Unit Tests Miss the Most Expensive Spring Application Bugs
&lt;/h2&gt;

&lt;p&gt;The most painful production outages rarely happen because of a misplaced &lt;code&gt;if&lt;/code&gt; statement. They happen at the &lt;strong&gt;seams&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A DTO field was renamed, silently breaking the JSON contract.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;@ConfigurationProperties&lt;/code&gt; stopped binding because of a property key change.&lt;/li&gt;
&lt;li&gt;  A transaction fails because a method is called from within the same bean, bypassing the Spring proxy.&lt;/li&gt;
&lt;li&gt;  A repository query is valid in theory but crashes against the real production schema.&lt;/li&gt;
&lt;li&gt;  Database migrations have drifted from the code.&lt;/li&gt;
&lt;li&gt;  Security filters start blocking requests after a configuration update.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;ObjectMapper&lt;/code&gt; is serializing dates in a format the frontend doesn't expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unit tests aren't built to catch these. Their domain is logic in isolation. &lt;strong&gt;Component tests&lt;/strong&gt; are exactly where these bugs go to die.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Component Test in Spring and Where Is Its Boundary?
&lt;/h2&gt;

&lt;p&gt;A component test verifies that a component works &lt;strong&gt;as a system&lt;/strong&gt; within its area of responsibility. In Spring Boot, this generally means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Starting a real Spring context (full or targeted).&lt;/li&gt;
&lt;li&gt; Calling the component through its boundary (HTTP, message handler, public service).&lt;/li&gt;
&lt;li&gt; Verifying the result along with its infrastructure side effects: transactions, serialization, validation, data access, and configurations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A practical template for a REST component test is &lt;code&gt;@SpringBootTest&lt;/code&gt; + a real HTTP client + a real database via &lt;a href="https://bitdive.io/java-component-tests/" rel="noopener noreferrer"&gt;Testcontainers&lt;/a&gt;. This isn't E2E across your entire microservices architecture; it’s a focused check of one service "exactly as it will live in the real world."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.test.context.SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.test.web.client.TestRestTemplate&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.http.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.testcontainers.containers.PostgreSQLContainer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.testcontainers.junit.jupiter.Container&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.testcontainers.junit.jupiter.Testcontainers&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.test.context.DynamicPropertyRegistry&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.test.context.DynamicPropertySource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webEnvironment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpringBootTest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WebEnvironment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;RANDOM_PORT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Testcontainers&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderComponentTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Container&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;PostgreSQLContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PostgreSQLContainer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"postgres:16-alpine"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;@DynamicPropertySource&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;props&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DynamicPropertyRegistry&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.datasource.url"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;pg:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getJdbcUrl&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.datasource.username"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;pg:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getUsername&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.datasource.password"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;pg:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;getPassword&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.jpa.hibernate.ddl-auto"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"validate"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Flyway/Liquibase will also run on context startup if present.&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="nc"&gt;TestRestTemplate&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_create_order_and_return_contract&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user-1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;util&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sku-1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
                &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;postForEntity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/orders"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CREATED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatusCode&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;assertNotNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBody&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;assertNotNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBody&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user-1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBody&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test simultaneously confirms that the Spring context boots, beans wire correctly, JSON contracts are intact, validation is working, and the repositories are compatible with the schema. These are the points of failure where production usually breaks, even while your unit tests stay perfectly green.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Keep Component Tests from Becoming "Slow Flaky Hell"
&lt;/h2&gt;

&lt;p&gt;A component test becomes toxic the moment it depends on an uncontrolled environment. If your test hits a "live" staging instance of an external service, it will inevitably flake due to network lag, data shifts, rate limits, or another team's deployment schedule.&lt;/p&gt;

&lt;p&gt;External dependencies at the component level must be either &lt;strong&gt;containerized&lt;/strong&gt; or &lt;strong&gt;stubbed&lt;/strong&gt; with a local contract server.&lt;/p&gt;

&lt;p&gt;The classic Spring case: you have a client for an external REST API. A unit test with mocks is too easy to "fool": you'll model an ideal response and miss issues with headers, serialization, 4xx/5xx errors, or timeouts. A component test should exercise the &lt;strong&gt;real HTTP stack&lt;/strong&gt;, but against a controlled stub server.&lt;/p&gt;

&lt;p&gt;Here’s an example using OkHttp &lt;code&gt;MockWebServer&lt;/code&gt;. You’re testing the real client, real serialization, and real error handling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.mockwebserver.MockResponse&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;okhttp3.mockwebserver.MockWebServer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io.IOException&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExternalApiClientComponentTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;MockWebServer&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@BeforeEach&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockWebServer&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@AfterEach&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;shutdown&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_map_success_response&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enqueue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockResponse&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setResponseCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{\"id\":\"42\",\"status\":\"OK\"}"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;ExternalApiClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExternalApiClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OK"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_throw_on_500&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enqueue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MockResponse&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setResponseCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="nc"&gt;ExternalApiClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ExternalApiClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;assertThrows&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ExternalApiException&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"42"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a component test for your "integration layer." It doesn't require the full Spring context, but it tests the system on the component level: HTTP + conversion + error states. In Spring, you often do this inside &lt;code&gt;@SpringBootTest&lt;/code&gt;, but the core remains: real protocols and seams must be verified in a controlled environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Do "Spring Slice Tests" Live Relative to Unit and Component?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@WebMvcTest&lt;/code&gt;, &lt;code&gt;@DataJpaTest&lt;/code&gt;, and &lt;code&gt;@JsonTest&lt;/code&gt; are not unit tests in the classical sense because they spin up infrastructure. They also aren't full component tests. They are &lt;strong&gt;"slices"&lt;/strong&gt; that allow you to cheaply verify a specific seam. &lt;/p&gt;

&lt;p&gt;For instance, &lt;code&gt;@DataJpaTest&lt;/code&gt; is perfect for checking if a repository works correctly on a real database schema without booting the entire web stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.junit.jupiter.api.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;static&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;junit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jupiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Assertions&lt;/span&gt;&lt;span class="o"&gt;.*;&lt;/span&gt;

&lt;span class="nd"&gt;@DataJpaTest&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepositorySliceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;should_find_by_email&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UserEntity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"a@b.com"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByEmail&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a@b.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isPresent&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves massive amounts of time when you need to test the JPA/SQL seam specifically, without the overhead of the entire service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "Both Levels" Is Not Theory: It's Pure ROI on Regressions
&lt;/h2&gt;

&lt;p&gt;If you rely solely on unit tests, your CI is fast and your logic is sound, but you &lt;strong&gt;pay for it with production incidents at the seams.&lt;/strong&gt; If you rely solely on component tests, you catch those seams, but your velocity drops, tests become expensive to maintain, and debugging turns into a nightmare.&lt;/p&gt;

&lt;p&gt;When these levels work together, they reinforce each other:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Unit tests&lt;/strong&gt; drastically reduce the number of potential root causes for component failures.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Component tests&lt;/strong&gt; guarantee that the real "glued together" application doesn't crumble due to Spring magic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where BitDive Fits: Reproducibility for Component Tests
&lt;/h2&gt;

&lt;p&gt;The most expensive part of component testing isn’t writing &lt;code&gt;@SpringBootTest&lt;/code&gt;. It’s &lt;strong&gt;reproducing real-world scenarios&lt;/strong&gt; and maintaining stubs for dozens of integrations.&lt;/p&gt;

&lt;p&gt;Ideally, component tests should verify what actually happens in production: real call chains, real input data, and real responses from external systems.&lt;/p&gt;

&lt;p&gt;BitDive's approach bridges this gap: you capture execution chains (REST, SQL, Kafka, etc.), parameters, and results, then replay them in tests &lt;strong&gt;deterministically&lt;/strong&gt;, substituting external interactions with the recorded dataset. This turns a component test from a "what-if" scenario into a &lt;strong&gt;guarantee that a real production case will never regress again.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How does a component test differ from an integration test?
&lt;/h3&gt;

&lt;p&gt;A component test verifies a single service "as a system" in a controlled environment: with a real Spring context, a real database (via Testcontainers), and stubbed external services. An integration test in the broader sense often involves multiple real services running together, which is slower and less stable.&lt;/p&gt;

&lt;h3&gt;
  
  
  When are unit tests enough?
&lt;/h3&gt;

&lt;p&gt;For "pure" business logic without infrastructure ties: calculations, validations, mappings, and complex transformations. If a method can run without Spring, a database, or HTTP, a unit test is your best friend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need @SpringBootTest for every component test?
&lt;/h3&gt;

&lt;p&gt;No. Spring Slice tests (&lt;code&gt;@WebMvcTest&lt;/code&gt;, &lt;code&gt;@DataJpaTest&lt;/code&gt;) boot only the required context slice and are much faster than a full &lt;code&gt;@SpringBootTest&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>java</category>
      <category>springboot</category>
      <category>testing</category>
    </item>
    <item>
      <title>Quality Assurance in AI Assisted Software Development: Risks and Implications</title>
      <dc:creator>Dmitry Turmyshev</dc:creator>
      <pubDate>Fri, 13 Feb 2026 01:47:33 +0000</pubDate>
      <link>https://dev.to/dmitry_turmyshev/quality-assurance-in-ai-assisted-software-development-risks-and-implications-34kk</link>
      <guid>https://dev.to/dmitry_turmyshev/quality-assurance-in-ai-assisted-software-development-risks-and-implications-34kk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"We're now cooperating with AIs and usually they are doing the generation and we as humans are doing the verification. It is in our interest to make this loop go as fast as possible. So, we're getting a lot of work done."&lt;/p&gt;

&lt;p&gt;— Andrej Karpathy: Software Is Changing (Again)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This quote describes a shift that is already visible in many teams. Code generation has accelerated. Verification and validation increasingly become the bottleneck.&lt;/p&gt;

&lt;p&gt;With AI tools, writing code is often not the limiting factor anymore. The hard part is proving that what was generated is correct, safe, and maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Volume Growth and Test Review Challenges
&lt;/h2&gt;

&lt;p&gt;To understand QA challenges, we should look at how code is produced. Testing is not isolated. It reflects development speed and development habits. If development accelerates, QA pressure grows too.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Main Shift: Writing Has Become Cheap, Verification Has Become Expensive
&lt;/h3&gt;

&lt;p&gt;A common side effect of AI coding is rapid codebase growth without matching growth in quality. This is often described as "code bloat".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some Facts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explosive Growth in Code Volume (&lt;a href="https://www.greptile.com/state-of-ai-coding-2025" rel="noopener noreferrer"&gt;Greptile, 2025&lt;/a&gt;):&lt;/strong&gt; The "State of AI Coding 2025" report recorded a 76% increase in output per developer. At the same time, the average size of a Pull Request (PR) increased by 33%. Physically, significantly more material arrives for verification and review than a person can qualitatively process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw38eyjs9vdew6wmn2elz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw38eyjs9vdew6wmn2elz.png" alt="Statistical chart showing a 76% increase in code volume per developer and a 33% increase in Pull Request sizes in 2025" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Code quality signals degrade (&lt;a href="https://www.gitclear.com/ai_assistant_code_quality_2025_research" rel="noopener noreferrer"&gt;GitClear, 2025&lt;/a&gt;):&lt;/strong&gt; A study covering 211 million lines of changed code (2020 to 2024) reports that the share of refactoring and code movement fell from about 25% to under 10% by 2024, while copy and paste style changes increased.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Delivery stability can suffer (DORA, v2025.2):&lt;/strong&gt; In &lt;a href="https://services.google.com/fh/files/misc/dora-impact-of-generative-ai-in-software-development.pdf" rel="noopener noreferrer"&gt;"Impact of Generative AI in Software Development"&lt;/a&gt;, DORA reports an estimated association: for every 25% increase in AI adoption, delivery throughput decreases by about 1.5% and delivery stability decreases by about 7.2%.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Anti-Patterns and "Review Fatigue"
&lt;/h3&gt;

&lt;p&gt;Code generated by AI often contains structural security errors and architectural "crutches" that a human expert would never write. Errors become more subtle and difficult to detect, as AI writes syntactically correct but logically vulnerable code.&lt;/p&gt;

&lt;p&gt;As the volume of generated code grows, human capacity for critical analysis decreases. The phenomenon of &lt;strong&gt;"Review Fatigue"&lt;/strong&gt; sets in.&lt;/p&gt;

&lt;p&gt;Engineers tends to trust correctly formatted code by default. The "Looks good to me" (LGTM) effect kicks in, where the reviewer's attention is dulled by the visual correctness of the generated solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Tests Become Unmaintainable: The Knowledge Gap Problem
&lt;/h2&gt;

&lt;p&gt;When test code grows faster than shared understanding, teams accumulate a critical knowledge gap.&lt;/p&gt;

&lt;p&gt;Before AI tools, writing an automated test was a cognitive process. The engineer had to study requirements and formulate verification conditions. The test served as documentation of this understanding.&lt;/p&gt;

&lt;p&gt;With AI assistants, hundreds of lines of test code can be generated in seconds, skipping this cognitive stage. When an AI-generated test fails, the engineer faces code they are seeing for the first time. The team's collective knowledge about &lt;em&gt;what exactly&lt;/em&gt; these tests verify approaches zero.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Safe Path" Trap and Mocking Hell
&lt;/h3&gt;

&lt;p&gt;AI models, being probabilistic, strive to minimize the risk of syntax errors. The safest path for the model is to write a test that calls a function but checks minimal conditions.&lt;/p&gt;

&lt;p&gt;This gets worse with heavy mocking. AI often produces verbose tests with many mocks. Such tests can lock onto implementation details instead of behavior. Then refactors break tests even when user-visible behavior stays the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Maintenance Costs with AI-Generated Code
&lt;/h2&gt;

&lt;p&gt;Generating code is cheap, maintaining it is not. AI tests are often tightly coupled to the internal structure of the code. When internals change, these tests fail.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.capgemini.com/insights/research-library/world-quality-report-2024-25/" rel="noopener noreferrer"&gt;World Quality Report 2025-26&lt;/a&gt;, 50% of QA leaders using AI in test case automation cite "Maintenance burden &amp;amp; flaky scripts" as a key challenge. "Resources are continually being depleted by maintenance."&lt;/p&gt;

&lt;h2&gt;
  
  
  How AI Changes Testing Practices: TDD and the Testing Pyramid
&lt;/h2&gt;

&lt;p&gt;The essence of TDD has always been not just about verification, but about &lt;strong&gt;design&lt;/strong&gt;. Writing a test &lt;em&gt;before&lt;/em&gt; code forced the engineer to think through the architecture. When AI generates implementation in seconds, this "thinking stage" is skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transformation of the Testing Pyramid
&lt;/h3&gt;

&lt;p&gt;The classic "Testing Pyramid" (many cheap Unit tests, few expensive E2E) is rapidly losing relevance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01uy5uyvmzkubl1ahtlm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F01uy5uyvmzkubl1ahtlm.png" alt="Comparison between the traditional testing pyramid with a wide unit test base and the new AI-inverted pyramid focused on E2E and integration tests" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AI inverts the classic testing pyramid. Since AI writes code faster than humans, the bottleneck becomes checking intentions. The emphasis shifts to integration and end-to-end (E2E) tests, where autonomous agents check the operability of the entire system.&lt;/p&gt;

&lt;p&gt;In an economy where coding approaches zero cost, value shifts from &lt;em&gt;writing lines&lt;/em&gt; to &lt;strong&gt;Intent Validation&lt;/strong&gt;. Achieving an 80-90% coverage indicator has become trivial, but the correlation between high coverage and actual product quality has practically disappeared. This requires &lt;a href="https://bitdive.io/blog/test-to-code-ratio-standards-2026/" rel="noopener noreferrer"&gt;updating test-to-code ratio standards&lt;/a&gt; for AI-assisted development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration and Contract Testing in Microservices
&lt;/h2&gt;

&lt;p&gt;Contract testing is becoming a critically important standard for microservices and API-First architecture. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Essence of the Difference:&lt;/strong&gt; Integration testing checks &lt;em&gt;real interaction&lt;/em&gt; of running services. Contract testing validates &lt;em&gt;compliance with agreements&lt;/em&gt; (contracts) in isolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  From Static Contracts to Behavior Validation
&lt;/h3&gt;

&lt;p&gt;The next evolutionary step is the transition from static, manually maintained contracts to dynamic behavior-based validation.&lt;/p&gt;

&lt;p&gt;Modern integration testing increasingly uses containerization. Libraries like &lt;strong&gt;Testcontainers&lt;/strong&gt; allow spinning up disposable instances of databases (PostgreSQL, Redis, Kafka) for each test run. This enables &lt;strong&gt;deep integration testing&lt;/strong&gt; with real dependencies, achieving E2E-level reliability but with the speed and isolation of Unit tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Healing Tests and E2E
&lt;/h2&gt;

&lt;p&gt;A key trend in E2E is using AI to combat locator fragility. If a developer changes a button ID, tools with "self-healing" find the element by other features (text, position, neighbors).&lt;/p&gt;

&lt;p&gt;However, adoption lags. The World Quality Report reports self-healing tests at 49% adoption and warns that self-healing scripts remain underused, leaving teams with fragile pipelines and rising maintenance costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Recommendations for QA Teams
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Reconsider Metrics
&lt;/h3&gt;

&lt;p&gt;Shift focus from coverage percentage to &lt;strong&gt;Mutation Testing&lt;/strong&gt; and &lt;strong&gt;Requirements Coverage&lt;/strong&gt;. What matters is whether the test fails if you actually break the logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Invest in Verification Infrastructure
&lt;/h3&gt;

&lt;p&gt;Implement &lt;strong&gt;Ephemeral Environments&lt;/strong&gt;. For each Pull Request, an isolated environment with real dependencies (via Testcontainers) should be automatically spun up.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Give AI Access to Context
&lt;/h3&gt;

&lt;p&gt;Integrate agents with runtime. AI should get access to container logs, traces, and test execution results to analyze failure causes effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;The main challenge of 2026 is learning to validate code faster than AI can generate it. We are moving away from a model where a human writes both code and tests, to a model where a human defines intentions and AI implements them under supervision.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codequality</category>
      <category>softwaredevelopment</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
