<?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>Forem: Martin Choraine</title>
    <description>The latest articles on Forem by Martin Choraine (@mchoraine).</description>
    <link>https://forem.com/mchoraine</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%2F426110%2F304fab18-ca5e-4b37-bb06-ba350b2ca792.jpeg</url>
      <title>Forem: Martin Choraine</title>
      <link>https://forem.com/mchoraine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mchoraine"/>
    <language>en</language>
    <item>
      <title>🧪 Speeding Up Spring Integration Tests: Lessons from Context Caching</title>
      <dc:creator>Martin Choraine</dc:creator>
      <pubDate>Sun, 20 Jul 2025 07:44:45 +0000</pubDate>
      <link>https://forem.com/mchoraine/speeding-up-spring-integration-tests-lessons-from-context-caching-d6p</link>
      <guid>https://forem.com/mchoraine/speeding-up-spring-integration-tests-lessons-from-context-caching-d6p</guid>
      <description>&lt;p&gt;At Pleenk, like in most projects I’ve worked on, the number of tests inevitably grows over time — and with it, the execution time. As feedback loops get longer, developers lose effectiveness, and eventually motivation to test. That’s when issues start to creep in.&lt;/p&gt;

&lt;p&gt;One common way to maintain fast feedback is to prioritize unit tests. They are great for testing individual components in isolation and for exploring edge cases. But they’re not enough. Nothing gives me as much confidence as a good integration test — one that verifies that components work correctly together in a real configuration.&lt;/p&gt;

&lt;p&gt;This is the story of a developer trying to reconcile confidence and speed, to write effective integration tests and ease the pressure on an overloaded CI pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ The Myth of the Minimal Context
&lt;/h2&gt;

&lt;p&gt;It all started with a simple intuition:&lt;br&gt;
&lt;em&gt;"If I reduce the Spring context size in my tests, they’ll run faster."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wrote a test with a minimal Spring context, loading only the configuration and beans strictly necessary. Result: from several dozen seconds (sometimes minutes) of bootstrap time, I went down to just a few seconds. Victory.&lt;/p&gt;

&lt;p&gt;Encouraged, I decided to apply this strategy more broadly. But when I tried to generalize it across all tests, the outcome was very different — and that’s when it all fell apart.&lt;br&gt;
Running the full suite led to &lt;em&gt;slower&lt;/em&gt; execution overall. A huge letdown.&lt;/p&gt;

&lt;p&gt;A single test ran fast, but running multiple tests together had the opposite effect. Why?&lt;/p&gt;


&lt;h2&gt;
  
  
  🔍 Understanding Spring’s Test Context Cache
&lt;/h2&gt;

&lt;p&gt;The culprit: &lt;strong&gt;Spring’s test context cache&lt;/strong&gt;, which I previously knew only superficially.&lt;/p&gt;

&lt;p&gt;After some digging, I discovered that Spring optimizes test time by &lt;strong&gt;sharing application contexts&lt;/strong&gt; across test classes — if it can. If the framework detects an identical context has already been built, it reuses it. Otherwise, it rebuilds one from scratch, which is costly.&lt;/p&gt;

&lt;p&gt;Spring generates a &lt;strong&gt;cache key&lt;/strong&gt; based on many aspects of test configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context Loader / Initializers&lt;/strong&gt;: via &lt;code&gt;@ContextConfiguration&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test-specific customizers&lt;/strong&gt;: &lt;code&gt;@DynamicPropertySource&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context hierarchy&lt;/strong&gt;: via &lt;code&gt;@ContextHierarchy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active profiles&lt;/strong&gt;: via &lt;code&gt;@ActiveProfiles("test")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test properties&lt;/strong&gt;: via &lt;code&gt;@TestPropertySource&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the key differs (or the context is marked &lt;code&gt;@DirtiesContext&lt;/code&gt;), Spring creates a new context.&lt;/p&gt;

&lt;p&gt;👉 Running a test in isolation with a minimal context is fast.&lt;br&gt;&lt;br&gt;
👉 But running many tests with slightly different contexts &lt;strong&gt;destroys cache efficiency&lt;/strong&gt; and adds overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson learned&lt;/strong&gt;: by trying to optimize individual test speed, I had accidentally killed shared context reuse across the suite.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 The &lt;code&gt;@MockBean&lt;/code&gt; Illusion
&lt;/h2&gt;

&lt;p&gt;Another tempting idea is to create a &lt;strong&gt;large, generic context&lt;/strong&gt; that can serve many tests — minimizing the number of context rebuilds.&lt;/p&gt;

&lt;p&gt;But quickly, reality hits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have to initialize &lt;em&gt;everything&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Deal with irrelevant side effects&lt;/li&gt;
&lt;li&gt;Prepare data for unrelated layers&lt;/li&gt;
&lt;li&gt;Understand complex interactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests become brittle, slow, hard to read.&lt;/p&gt;

&lt;p&gt;Enter &lt;code&gt;@MockBean&lt;/code&gt;, a widely used solution to isolate parts of the application.&lt;/p&gt;

&lt;p&gt;And at first glance, it’s perfect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simulate irrelevant dependencies&lt;/li&gt;
&lt;li&gt;Keep tests focused&lt;/li&gt;
&lt;li&gt;Code remains clean and controlled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here’s the hidden cost: each use of &lt;code&gt;@MockBean&lt;/code&gt; &lt;strong&gt;alters the context configuration&lt;/strong&gt;. Even adding or removing a single mock &lt;strong&gt;breaks the cache key&lt;/strong&gt;, causing Spring to rebuild the entire context.&lt;/p&gt;

&lt;p&gt;What seems minor for one test becomes a massive overhead across a suite.&lt;code&gt;@MockBean&lt;/code&gt; is powerful — but &lt;strong&gt;extremely dangerous&lt;/strong&gt; if you care about fast execution. Yet, it’s ubiquitous. I’ve seen it in most projects, training courses, and online tutorials.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧱 Modular Monolith Architecture to the Rescue
&lt;/h2&gt;

&lt;p&gt;At Pleenk, our application is a &lt;strong&gt;modular monolith&lt;/strong&gt; — a single deployable unit structured into business modules. Each module is strictly isolated according to &lt;strong&gt;hexagonal architecture principles&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separation of domain logic from infrastructure&lt;/li&gt;
&lt;li&gt;No direct calls between business modules&lt;/li&gt;
&lt;li&gt;Communication via ports, gateways, or events&lt;/li&gt;
&lt;li&gt;Clearly defined and controlled dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We call this a &lt;strong&gt;modulith&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A design that combines the &lt;strong&gt;deployment simplicity of a monolith&lt;/strong&gt; with the &lt;strong&gt;modularity and maintainability&lt;/strong&gt; of microservice-inspired architectures.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most integration tests only target &lt;strong&gt;one or two modules&lt;/strong&gt;, never the entire app.&lt;/p&gt;

&lt;p&gt;So why not &lt;strong&gt;configure only the modules we care about&lt;/strong&gt; — and &lt;strong&gt;mock the rest&lt;/strong&gt;?&lt;/p&gt;


&lt;h2&gt;
  
  
  🛠️ The Solution: Declare Modules, Mock the Rest
&lt;/h2&gt;

&lt;p&gt;The goal: test a selected part of the application, while &lt;strong&gt;mocking all unrelated modules&lt;/strong&gt; — &lt;em&gt;without breaking the context cache&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  🎯 Strategy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Dynamically declare which modules to load&lt;/li&gt;
&lt;li&gt;Mock beans from all other modules&lt;/li&gt;
&lt;li&gt;Preserve a stable cache key so that contexts are reused across tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After much research, I found no out-of-the-box tool. But I stumbled on an overlooked Spring API: &lt;code&gt;ContextCustomizerFactory&lt;/code&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  1. Custom &lt;code&gt;ContextCustomizerFactory&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This interface lets you &lt;strong&gt;customize the application context&lt;/strong&gt; at test time.&lt;/p&gt;

&lt;p&gt;Our implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads the list of target modules from a custom annotation&lt;/li&gt;
&lt;li&gt;Detects beans belonging to other modules&lt;/li&gt;
&lt;li&gt;Replaces them with mocks (via Mockito)&lt;/li&gt;
&lt;li&gt;Defines a cache key based only on the selected modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 All tests targeting the same module set reuse the &lt;strong&gt;same Spring context&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This factory is used to customize the test context based on the selected modules&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModuleTestContextCustomizerFactory&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ContextCustomizerFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;testClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
        &lt;span class="n"&gt;configAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContextConfigurationAttributes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Check if the test class is annotated with @ModuleTest&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;testClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAnnotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ModuleTest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;annotation&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;ModuleTestContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModuleTestContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// These are the modules we want to include&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ContextCustomizer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;customizeContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ConfigurableApplicationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;mergedConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MergedContextConfiguration&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Inject a BeanFactoryPostProcessor to customize beans before instantiation&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addBeanFactoryPostProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;configureBeanFactory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;configureBeanFactory&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;BeanFactoryPostProcessor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeanFactoryPostProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beanDefinitionNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;beanName&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;beanDefinition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBeanDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;className&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;beanDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beanClassName&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shouldBeMocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;mockBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ModuleTestContextCustomizer&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contentEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;hashCode&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contentHashCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;shouldBeMocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.pleenk.backend.modules."&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isInTargetModules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".${module.packageName}."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isInTargetModules&lt;/span&gt; &lt;span class="c1"&gt;// Only mock if the bean doesn't belong to the selected modules&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;mockBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ConfigurableListableBeanFactory&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;beanClass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mockito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. A Clear Annotation: &lt;code&gt;@ModuleTest&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We introduced a custom annotation to define the modules needed by the test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ModuleTest&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PROFILE&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationIntegrationTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its Kotlin definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AnnotationTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AnnotationTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CLASS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Retention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AnnotationRetention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RUNTIME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="nd"&gt;@AutoConfigureMockMvc&lt;/span&gt;
&lt;span class="nd"&gt;@ActiveProfiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ContextCustomizerFactories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ModuleTestContextCustomizerFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;annotation&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModuleTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It wraps common test setup annotations and passes configuration to the factory.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Cleaner Injection: &lt;code&gt;@AutowiredMock&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;To improve readability, we added an &lt;code&gt;@AutowiredMock&lt;/code&gt; annotation — which behaves like &lt;code&gt;@Autowired&lt;/code&gt;, but signals that the bean is mock-injected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ModuleTest&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PROFILE&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationIntegrationTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@AutowiredMock&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;kycApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;KycApi&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This small detail makes it easier to distinguish &lt;strong&gt;real vs simulated dependencies&lt;/strong&gt; at a glance.&lt;/p&gt;




&lt;h2&gt;
  
  
  📉 The Results
&lt;/h2&gt;

&lt;p&gt;With this strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;50% faster&lt;/strong&gt; overall integration test execution&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Fewer contexts created&lt;/strong&gt; thanks to smarter reuse&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Consistent test design&lt;/strong&gt; across the team&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers now write tests that are more focused, faster, and easier to maintain.CI is more stable. Feedback loops are tighter. And most importantly: confidence is back.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 What’s Next?
&lt;/h2&gt;

&lt;p&gt;This strategy is still evolving. Some ideas we’re exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable &lt;code&gt;org.springframework.test.context.cache&lt;/code&gt; logs to track context reuse&lt;/li&gt;
&lt;li&gt;Avoid loading internal beans from modules that are only accessed via HTTP or controllers&lt;/li&gt;
&lt;li&gt;Write an internal test strategy guide to align team practices&lt;/li&gt;
&lt;li&gt;Build a tool to &lt;strong&gt;analyze test context reuse&lt;/strong&gt;: identify which tests share or break contexts, and which config deltas cause unnecessary context rebuilds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Got ideas or feedback?&lt;/strong&gt; Share them in the comments or reach out — I’m always interested in learning new test strategies for complex Spring apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;By combining modular design, precise context control, and a few Spring internals, we’ve found a sweet spot between &lt;strong&gt;test speed, confidence, and maintainability&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your team is facing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow CI builds&lt;/li&gt;
&lt;li&gt;Flaky or long-running integration tests&lt;/li&gt;
&lt;li&gt;Inconsistent test structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope this experience gives you some inspiration.There’s no silver bullet — but a thoughtful strategy can make &lt;strong&gt;a big difference&lt;/strong&gt; in both product quality and developer happiness.&lt;/p&gt;

</description>
      <category>spring</category>
      <category>testing</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>🧪 Réduire le temps d'exécution des tests d’intégration Spring : retour d’expérience sur le cache de contexte</title>
      <dc:creator>Martin Choraine</dc:creator>
      <pubDate>Sun, 20 Jul 2025 07:37:19 +0000</pubDate>
      <link>https://forem.com/mchoraine/reduire-le-temps-dexecution-des-tests-dintegration-spring-retour-dexperience-sur-le-cache-de-36b8</link>
      <guid>https://forem.com/mchoraine/reduire-le-temps-dexecution-des-tests-dintegration-spring-retour-dexperience-sur-le-cache-de-36b8</guid>
      <description>&lt;p&gt;Chez Pleenk, comme sur la plupart des projets où j’ai travaillé, le nombre de tests finit toujours par exploser. Et avec lui, le temps d'exécution. Résultat : les feedback loops s’allongent, les développeurs deviennent moins efficaces et finissent par se détourner des tests. C'est à ce moment-là que les problèmes apparaissent.&lt;/p&gt;

&lt;p&gt;Une idée très répandue pour obtenir un feedback rapide consiste à favoriser les tests unitaires. Ce sont eux qui permettent de tester chaque composant de manière isolée et d’explorer des cas extrêmes. Mais ils ne suffisent pas. Rien ne me donne autant de confiance qu’un test d’intégration, qui garantit que plusieurs composants fonctionnent bien ensemble, dans leur environnement réel.&lt;/p&gt;

&lt;p&gt;Voici donc l’histoire d’un développeur qui a voulu concilier confiance et rapidité, écrire des tests d’intégration efficaces, et soulager une CI en surchauffe.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Le mythe du contexte minimal
&lt;/h2&gt;

&lt;p&gt;Tout a commencé par une intuition :&lt;br&gt;&lt;br&gt;
&lt;em&gt;"Si je réduis la taille du contexte Spring dans mes tests, je vais les accélérer."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;J’ai donc créé un test avec un contexte très limité, ne chargeant que la configuration et les beans strictement nécessaires. Résultat : je suis passé de plusieurs dizaines de secondes, voire minutes de bootstrap, à &lt;strong&gt;quelques secondes&lt;/strong&gt;. Victoire.&lt;/p&gt;

&lt;p&gt;Motivé par ce succès, je décide de généraliser l’approche. Mais lorsque j’ai tenté d’appliquer cette stratégie à l’ensemble des tests, les résultats ont été tout autres. Et c’est là que tout s’écroule.&lt;br&gt;&lt;br&gt;
En exécutant toute la suite de tests, le &lt;strong&gt;temps global explose&lt;/strong&gt;. Plus lent qu’avant. Douche froide.&lt;br&gt;&lt;br&gt;
Le test lancé seul s'exécute plus vite, mais c'est l'effet inverse lorsque plusieurs tests sont lancés ensemble. Comment expliquer ce phénomène ?&lt;/p&gt;


&lt;h2&gt;
  
  
  🔍 Plongée dans le cache de contexte Spring
&lt;/h2&gt;

&lt;p&gt;Ce phénomène vient du &lt;strong&gt;cache de contexte Spring&lt;/strong&gt; dans les tests, que je ne connaissais alors que de loin.&lt;/p&gt;

&lt;p&gt;Après quelques recherches, je découvre que le framework propose un mécanisme pour réduire le temps d'exécution en &lt;strong&gt;mutualisant les contextes&lt;/strong&gt; entre les classes de test. Si le contexte a déjà été construit, il peut être réutilisé — sinon, il est reconstruit, ce qui coûte cher.&lt;/p&gt;

&lt;p&gt;Spring utilise une &lt;strong&gt;clé de cache&lt;/strong&gt; composée de nombreux éléments pour déterminer si un contexte peut être réutilisé :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context Loader / Initializers&lt;/strong&gt; : via &lt;code&gt;@ContextConfiguration&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test-specific customizers&lt;/strong&gt; : &lt;code&gt;@DynamicPropertySource&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context hierarchy&lt;/strong&gt; : via &lt;code&gt;@ContextHierarchy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active profiles&lt;/strong&gt; : via &lt;code&gt;@ActiveProfiles("test")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test properties&lt;/strong&gt; : via &lt;code&gt;@TestPropertySource&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si la clé change (ou si le contexte est marqué &lt;code&gt;@DirtiesContext&lt;/code&gt;), Spring considère qu’il doit &lt;strong&gt;reconstruire entièrement&lt;/strong&gt; le contexte.&lt;/p&gt;

&lt;p&gt;👉 Lancer un test isolé avec un contexte minimal est très rapide.&lt;br&gt;&lt;br&gt;
👉 Mais lancer toute la suite avec des dizaines de contextes légèrement différents &lt;strong&gt;coûte très cher&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moralité&lt;/strong&gt; : en essayant d’optimiser les tests isolés, j'avais en réalité détruit le partage du contexte entre eux.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧪 L’illusion du &lt;code&gt;@MockBean&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Face à ça, une autre idée paraît séduisante :&lt;br&gt;&lt;br&gt;
&lt;strong&gt;construire un contexte Spring suffisamment général pour être partagé entre tous les tests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mais on se heurte vite à un mur :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;il faut tout initialiser,&lt;/li&gt;
&lt;li&gt;gérer des interactions inutiles pour le test,&lt;/li&gt;
&lt;li&gt;injecter des jeux de données dans chaque couche,&lt;/li&gt;
&lt;li&gt;comprendre des comportements qui ne concernent même pas le test…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les tests deviennent alors longs, fragiles, illisibles.&lt;/p&gt;

&lt;p&gt;On en vient alors à une pratique très répandue : &lt;strong&gt;utiliser &lt;code&gt;@MockBean&lt;/code&gt; à outrance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Et en surface, c’est parfait :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on simule les dépendances non pertinentes,&lt;/li&gt;
&lt;li&gt;on garde un test ciblé,&lt;/li&gt;
&lt;li&gt;le code reste clair.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mais cette simplicité a un &lt;strong&gt;coût caché&lt;/strong&gt; : chaque &lt;code&gt;@MockBean&lt;/code&gt; &lt;strong&gt;modifie la définition du contexte&lt;/strong&gt; et casse la clé de cache.&lt;br&gt;&lt;br&gt;
Ce qui peut sembler anodin à l’échelle d’un test devient, à l’échelle d’une suite complète, un véritable multiplicateur de lenteur :&lt;br&gt;&lt;br&gt;
Spring &lt;strong&gt;reconstruit un nouveau contexte à chaque test&lt;/strong&gt;, même si un seul mock change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@MockBean&lt;/code&gt; apparaît donc comme un outil puissant… mais &lt;strong&gt;extrêmement dangereux&lt;/strong&gt; si l'on cherche à optimiser les temps d’exécution. Et pourtant, c’est une approche très répandue : je l’ai retrouvée dans la majorité des projets, des formations, et des tutoriels que j’ai croisés.&lt;/p&gt;


&lt;h2&gt;
  
  
  🧱 Une architecture modulitique pour structurer les tests
&lt;/h2&gt;

&lt;p&gt;Chez Pleenk, notre application est un &lt;strong&gt;monolithe structuré en modules métiers&lt;/strong&gt;. Chaque module est isolé selon les principes de &lt;strong&gt;l’architecture hexagonale&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;séparation stricte entre métier et infrastructure,&lt;/li&gt;
&lt;li&gt;pas d’appel direct entre modules métier,&lt;/li&gt;
&lt;li&gt;communication uniquement via ports, gateways ou événements,&lt;/li&gt;
&lt;li&gt;dépendances bien maîtrisées et explicites.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous appelons ça une &lt;strong&gt;architecture modulitique&lt;/strong&gt; :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Une approche qui combine la &lt;strong&gt;simplicité de déploiement d’un monolithe&lt;/strong&gt; avec la &lt;strong&gt;modularité et la maintenabilité&lt;/strong&gt; d’une architecture orientée microservices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Je ne vais pas entrer dans les détails ici — cette architecture mériterait son propre article — mais il faut juste comprendre qu’elle nous permet de &lt;strong&gt;limiter les interactions entre modules&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Et côté tests, on observe que &lt;strong&gt;chaque test d’intégration couvre un ou deux modules maximum&lt;/strong&gt;, jamais toute l’application.&lt;/p&gt;

&lt;p&gt;Dans ce cas, pourquoi ne pas &lt;strong&gt;configurer uniquement les modules concernés… et simuler les autres&lt;/strong&gt; ?&lt;/p&gt;


&lt;h2&gt;
  
  
  🛠️ La solution ?
&lt;/h2&gt;

&lt;p&gt;L’idée : tester une partie de l’application (un ou deux modules), tout en &lt;strong&gt;isolant intelligemment les autres&lt;/strong&gt;, sans casser le cache Spring.&lt;/p&gt;
&lt;h3&gt;
  
  
  🎯 Objectifs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Déclarer dynamiquement les modules à inclure,&lt;/li&gt;
&lt;li&gt;Moquer les beans des autres modules,&lt;/li&gt;
&lt;li&gt;Conserver une clé de cache stable pour mutualiser les contextes entre les tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Après de longues recherches (et quelques désillusions), je n’ai trouvé &lt;strong&gt;aucun outil clé en main&lt;/strong&gt; pour faire ça.&lt;br&gt;&lt;br&gt;
Mais en explorant les entrailles de Spring, j’ai découvert une API peu connue : &lt;code&gt;ContextCustomizerFactory&lt;/code&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  1. &lt;code&gt;ContextCustomizerFactory&lt;/code&gt; sur-mesure
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ContextCustomizerFactory&lt;/code&gt; est une interface qui permet de &lt;strong&gt;personnaliser la création du contexte d’application&lt;/strong&gt; pour les tests.&lt;/p&gt;

&lt;p&gt;Notre implémentation :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;récupère la liste des modules à booter,&lt;/li&gt;
&lt;li&gt;identifie les beans des autres modules,&lt;/li&gt;
&lt;li&gt;les remplace par des mocks, des implémentations factices ou spécifiques,&lt;/li&gt;
&lt;li&gt;définit une &lt;strong&gt;clé de cache basée uniquement sur les modules inclus&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Tous les tests ciblant les mêmes modules partagent alors &lt;strong&gt;le même contexte Spring&lt;/strong&gt;, même s’ils diffèrent sur d’autres points.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModuleTestContextCustomizerFactory&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ContextCustomizerFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;testClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
        &lt;span class="n"&gt;configAttributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ContextConfigurationAttributes&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// On vérifie que l’annotation @ModuleTest est présente sur la classe de test&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;testClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAnnotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ModuleTest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;annotation&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nc"&gt;ModuleTestContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Customizer principal, qui injecte les mocks au moment de la création du contexte&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModuleTestContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PleenkModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ContextCustomizer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;customizeContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ConfigurableApplicationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;mergedConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MergedContextConfiguration&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Ajout d’un post-processor pour intervenir sur les beans avant leur création&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addBeanFactoryPostProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;configureBeanFactory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;configureBeanFactory&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;BeanFactoryPostProcessor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeanFactoryPostProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beanDefinitionNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;beanName&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;beanDefinition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBeanDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;beanClassName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;beanDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beanClassName&lt;/span&gt;

                &lt;span class="c1"&gt;// Si le bean appartient à un module externe non testé, on le remplace par un mock&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shouldMockBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanClassName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;mockBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beanClassName&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Utilisé par le cache de Spring pour déterminer si le contexte peut être réutilisé&lt;/span&gt;
        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="p"&gt;===&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;ModuleTestContextCustomizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contentEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;hashCode&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contentHashCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;// Vérifie si un bean doit être mocké : il doit faire partie d’un module "externe"&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;shouldMockBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanClassName&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;isExternalModuleBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanClassName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;

            &lt;span class="c1"&gt;// Si le bean appartient à un des modules testés, on ne le mocke pas&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isFromTestedModule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;beanClassName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".${module.packageName}."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isFromTestedModule&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Remplace un bean par un mock Mockito&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;mockBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;beanClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ConfigurableListableBeanFactory&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;beanClass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanClassName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;mock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mockito&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;beanName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Filtre les beans qui viennent bien d’un module applicatif Pleenk&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isExternalModuleBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"""com\.pleenk\.backend\.modules\..*"""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRegex&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. Une annotation explicite : &lt;code&gt;@ModuleTest&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Pour transmettre la liste des modules ciblés à notre &lt;code&gt;ContextCustomizerFactory&lt;/code&gt;, nous avons créé une annotation maison :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ModuleTest&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PROFILE&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationIntegrationTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voici son implémentation (en Kotlin dans notre cas) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AnnotationTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TYPE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AnnotationTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CLASS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Retention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AnnotationRetention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RUNTIME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@SpringBootTest&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="nd"&gt;@AutoConfigureMockMvc&lt;/span&gt;
&lt;span class="nd"&gt;@ActiveProfiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ContextCustomizerFactories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ModuleTestContextCustomizerFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;annotation&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModuleTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Elle simplifie aussi le setup des tests : plus besoin de multiplier les annotations, tout est encapsulé dans &lt;code&gt;@ModuleTest&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Une injection explicite, lisible et familière
&lt;/h3&gt;

&lt;p&gt;Dans le test, il ne nous reste plus qu’à &lt;strong&gt;injecter les beans réels ou mockés&lt;/strong&gt;, comme on le ferait avec &lt;code&gt;@MockBean&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour améliorer la &lt;strong&gt;lisibilité&lt;/strong&gt; des tests, nous avons aussi introduit une annotation &lt;code&gt;@AutowiredMock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Elle ne fait rien de plus qu’un &lt;code&gt;@Autowired&lt;/code&gt;, mais permet d’exprimer clairement l’intention et d'aider à la relecture : &lt;em&gt;"ce bean est injecté sous forme mockée dans le contexte."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Voici à quoi ressemble un test typique aujourd’hui :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@ModuleTest&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NOTIFICATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PROFILE&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationIntegrationTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@AutowiredMock&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;kycApi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;KycApi&lt;/span&gt;

    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ce petit détail aide beaucoup à &lt;strong&gt;rendre les tests plus lisibles&lt;/strong&gt;, en distinguant clairement ce qui est "métier" de ce qui est "simulé".&lt;/p&gt;




&lt;h2&gt;
  
  
  📉 Résultats
&lt;/h2&gt;

&lt;p&gt;Avec cette stratégie :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Temps d’exécution &lt;strong&gt;divisé par deux&lt;/strong&gt; pour nos tests d’intégration,&lt;/li&gt;
&lt;li&gt;✅ Forte &lt;strong&gt;réduction du nombre de contextes créés&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;✅ Meilleure &lt;strong&gt;homogénéité&lt;/strong&gt; dans la façon d’écrire les tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les développeurs écrivent des tests plus ciblés, plus lisibles, plus rapides.&lt;br&gt;&lt;br&gt;
La CI est plus stable. Le feedback est plus rapide. La confiance revient.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Pour aller plus loin
&lt;/h2&gt;

&lt;p&gt;Cette stratégie est encore jeune, et nous continuons de l’améliorer.&lt;br&gt;&lt;br&gt;
Voici quelques pistes que nous explorons ou utilisons en parallèle :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Activer &lt;code&gt;org.springframework.test.context.cache&lt;/code&gt; en &lt;strong&gt;DEBUG&lt;/strong&gt; pour surveiller la création, la réutilisation et l’invalidation des contextes.&lt;/li&gt;
&lt;li&gt;Éviter de charger certains beans internes inutiles lorsque les tests n’interagissent qu’avec la couche d’exposition.&lt;/li&gt;
&lt;li&gt;Documenter la stratégie dans un &lt;strong&gt;guide d’équipe&lt;/strong&gt; pour harmoniser les pratiques de test.&lt;/li&gt;
&lt;li&gt;Développer un &lt;strong&gt;outil d’analyse des contextes Spring utilisés en test&lt;/strong&gt; :
L’objectif serait d’identifier plus facilement &lt;strong&gt;quels tests partagent un même contexte&lt;/strong&gt;, &lt;strong&gt;quels tests en créent un nouveau&lt;/strong&gt;, et &lt;strong&gt;quelles variations brisent la mutualisation&lt;/strong&gt;.
Ce type d’outil nous aiderait à &lt;strong&gt;rationaliser les contextes existants&lt;/strong&gt;, mieux comprendre les zones d’optimisation, et favoriser la convergence vers une architecture de test plus homogène.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Partagez vos idées ou retours d’expérience en commentaire ou contactez-moi directement — je suis toujours curieux de découvrir d’autres approches !&lt;/p&gt;




&lt;h2&gt;
  
  
  🧭 Conclusion
&lt;/h2&gt;

&lt;p&gt;En combinant une architecture modulitique, un contrôle précis du contexte Spring, et quelques outils bien choisis, nous avons trouvé un &lt;strong&gt;compromis durable entre vitesse, confiance et maintenabilité.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si vous faites face à :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;des temps de build trop longs,&lt;/li&gt;
&lt;li&gt;des tests lents ou instables,&lt;/li&gt;
&lt;li&gt;ou un manque d’uniformité dans les pratiques de test…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;J’espère que ce retour d’expérience vous inspirera.&lt;br&gt;&lt;br&gt;
Il n’y a pas de magie, mais une stratégie réfléchie peut vraiment faire &lt;strong&gt;la différence dans la qualité de vos tests — et de votre quotidien de dev.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>spring</category>
      <category>testing</category>
      <category>java</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Configure Angular environment injector with Standalone Component</title>
      <dc:creator>Martin Choraine</dc:creator>
      <pubDate>Thu, 05 Jan 2023 14:57:53 +0000</pubDate>
      <link>https://forem.com/zenika/configure-angular-environment-injector-with-standalone-component-3ncl</link>
      <guid>https://forem.com/zenika/configure-angular-environment-injector-with-standalone-component-3ncl</guid>
      <description>&lt;p&gt;Since the arrival of Standalones Component with &lt;strong&gt;Angular 15&lt;/strong&gt;, &lt;code&gt;NgModules&lt;/code&gt; are now completely optional. &lt;/p&gt;

&lt;p&gt;It is still necessary to declare providers to configure the root injector of the application.&lt;/p&gt;

&lt;p&gt;With modules, it is possible to import other NgModules that define their own providers like &lt;code&gt;HttpClientModule&lt;/code&gt;, &lt;code&gt;BrowserAnimationsModule&lt;/code&gt;, &lt;code&gt;RouterModule&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Without the modules, it is necessary to declare these providers when bootstrapping the application. Fortunately, there are functions provided by angular to facilitate the declaration of these providers. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;bootstrapApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;provideAnimations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a non-exhaustive list of functions to set up regularly used providers : &lt;/p&gt;

&lt;h2&gt;
  
  
  Core
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;importProvidersFrom()&lt;/code&gt;: Sets up providers collected from a given module&lt;/p&gt;

&lt;h2&gt;
  
  
  Animation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;provideAnimations()&lt;/code&gt;: Enable animation&lt;br&gt;
&lt;code&gt;provideNoopAnimations()&lt;/code&gt;: Disable animation&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;provideHttpClient()&lt;/code&gt;: Sets up HttpClient service and configures it with some features&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;withInterceptors()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withInterceptorsFromDi()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withXsrfConfiguration()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withNoXsrfProtection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withJsonpSupport()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withRequestsMadeViaParent()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;provideHttpClientTesting()&lt;/code&gt;: Sets up a mocked HttpClient service&lt;/p&gt;

&lt;h2&gt;
  
  
  Router
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;provideRouter()&lt;/code&gt;: Sets up router functionality for the application&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;withInMemoryScrolling()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withEnabledBlockingInitialNavigation()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withDisabledInitialNavigation()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withDebugTracing()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withPreloading()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;withRouterConfig()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;provideLocationMocks()&lt;/code&gt;: Sets up a mocked router for testing&lt;/p&gt;

&lt;h2&gt;
  
  
  NgRx
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;provideStore()&lt;/code&gt;: Sets up the global store &lt;br&gt;
&lt;code&gt;provideState()&lt;/code&gt;: Provides additional slices of state in the store&lt;br&gt;
&lt;code&gt;provideEffects()&lt;/code&gt;: Sets up given effects&lt;br&gt;
&lt;code&gt;provideStoreDevtools()&lt;/code&gt;: Sets up debug functionality&lt;br&gt;
&lt;code&gt;provideMockStore()&lt;/code&gt;: Sets up a mocked store&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>website</category>
    </item>
    <item>
      <title>Angular Universal with Standalone Component</title>
      <dc:creator>Martin Choraine</dc:creator>
      <pubDate>Sat, 17 Dec 2022 19:52:32 +0000</pubDate>
      <link>https://forem.com/zenika/angular-universal-with-standalone-component-206c</link>
      <guid>https://forem.com/zenika/angular-universal-with-standalone-component-206c</guid>
      <description>&lt;p&gt;With the arrival of Standalone Component with Angular 15, it is now possible to do without &lt;code&gt;@NgModules&lt;/code&gt; entirely. This opens the door to new possibilities.&lt;/p&gt;

&lt;p&gt;It is now possible to bootstrap an Angular application by providing an entry point component with a list of providers in the &lt;code&gt;main.ts&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;bootstrapApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideAnimations&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, with this new approach, it is not yet possible to simply take advantage of the server side rendering (SSR) with Angular Universal because the function &lt;code&gt;ngExpressEngine&lt;/code&gt; only takes a module parameter and not a component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ngExpressEngine&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nguniversal/express-engine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Set the engine&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Bootstrap and render NgModule with express adapter for `@angular/platform-server`&lt;/span&gt;
  &lt;span class="nf"&gt;ngExpressEngine&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// Take a module but we would like to provide component&lt;/span&gt;
    &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServerAppModule&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;view engine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/**/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../dist/index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the other hand it is possible to use manually the render function provided by &lt;code&gt;@angular/platform-server&lt;/code&gt;. &lt;br&gt;
This forces us to redo the work done by Angular Universal inside &lt;code&gt;ngExpressEngine&lt;/code&gt; but it's the only possibility while waiting for the support of standalone component  :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderApplication&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/platform-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Bootstrap and render a Standalone Component &lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;renderApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;server-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;view engine&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/**/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../dist/index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to the function &lt;code&gt;renderApplication&lt;/code&gt;, it is possible to bootstrap an Angular component and render it on the server side while waiting for the full support of Standalone Component in Angular Universal with the provided function &lt;code&gt;ngExpressEngine&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>ssr</category>
    </item>
  </channel>
</rss>
