<?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: Alvaro Lopez</title>
    <description>The latest articles on DEV Community by Alvaro Lopez (@lopz).</description>
    <link>https://dev.to/lopz</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3063935%2F0fe6b3ff-e49e-49fa-bf3b-65b330aa2ec1.jpg</url>
      <title>DEV Community: Alvaro Lopez</title>
      <link>https://dev.to/lopz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lopz"/>
    <language>en</language>
    <item>
      <title>Why mocking your Vector DB in CI is a terrible idea (and a 200ms fix)</title>
      <dc:creator>Alvaro Lopez</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:39:15 +0000</pubDate>
      <link>https://dev.to/lopz/why-mocking-your-vector-db-in-ci-is-a-terrible-idea-and-a-200ms-fix-4nmc</link>
      <guid>https://dev.to/lopz/why-mocking-your-vector-db-in-ci-is-a-terrible-idea-and-a-200ms-fix-4nmc</guid>
      <description>&lt;p&gt;Over the last year, building a RAG application has become incredibly easy. But if you’ve tried to move that prototype from a Jupyter Notebook to a production-grade CI/CD pipeline, you’ve probably hit a brick wall.&lt;/p&gt;

&lt;p&gt;Testing vector search in a GitHub Actions runner without melting the server is a logistical nightmare. When setting up &lt;code&gt;pytest&lt;/code&gt; for an app that relies on ChromaDB or Qdrant, engineering teams usually fall into one of two traps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 1: The Mocking Illusion
&lt;/h3&gt;

&lt;p&gt;You decide to mock the database client using &lt;code&gt;unittest.mock&lt;/code&gt;. Your test asserts that &lt;code&gt;collection.query()&lt;/code&gt; was called with the right parameters. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it fails:&lt;/strong&gt; You aren't actually testing anything useful. The entire point of a RAG pipeline is semantic search, the cosine similarity thresholds, embedding dimensions, and chunk retrieval. If you mock the vector database, you are flying blind. You’ll deploy to production only to realize your distance metrics were completely wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 2: The DinD Heavyweight
&lt;/h3&gt;

&lt;p&gt;You decide to do it right and use Testcontainers to spin up a real ChromaDB instance in your CI workflow. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it fails:&lt;/strong&gt; Running Docker-in-Docker (DinD) in a GitHub runner (which gives you 7GB of RAM) is painfully slow. Pulling gigabytes of vector database images and booting the daemon consumes massive amounts of memory. Your CI pipeline goes from 45 seconds to 10 minutes, or simply crashes with an &lt;code&gt;OOMKilled&lt;/code&gt; error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separating Compute from State
&lt;/h3&gt;

&lt;p&gt;The problem isn’t the database; it’s where we are running it. We are forcing our CI compute runners to also act as infrastructure provisioners. &lt;/p&gt;

&lt;p&gt;The ideal architecture for testing is separating the two. Your CI runner should do nothing but execute your test code. The database should live on a remote, isolated server, boot instantly, and destroy itself the moment the test finishes.&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;strong&gt;TrashDB&lt;/strong&gt;. It’s an orchestration API (built in .NET 10 running on bare-metal) designed to do one thing: spin up databases in 200 milliseconds and kill them.&lt;/p&gt;

&lt;p&gt;I recently shipped a native Python SDK so you can integrate it directly into your &lt;code&gt;pytest&lt;/code&gt; fixtures without writing complex bash teardown scripts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;trashdb&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TrashDBClient&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrashDBClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Spins up an ephemeral ChromaDB container in ~200ms
&lt;/span&gt;    &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chromadb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl_minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;

    &lt;span class="c1"&gt;# Cleans up the remote server automatically
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;terminate_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_rag_retrieval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;chroma_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;vector_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Your actual semantic search tests go here...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By externalizing the database, your runner only executes Python code. No heavy image pulls, no OOM crashes, and you can run multiple jobs in parallel without data collisions.&lt;/p&gt;

&lt;p&gt;I’m building TrashDB in public and it's currently in a free Alpha. If you are struggling with testing your AI apps in CI, grab an API key at &lt;a href="https://trashdb.dev" rel="noopener noreferrer"&gt;trashdb.dev&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Are you currently team mock, team Testcontainers, or something else entirely? Let me know below.&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>devops</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
