<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://prototest.dev/blog/</id>
    <title>ProtoTest Blog</title>
    <updated>2026-10-02T18:35:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://prototest.dev/blog/"/>
    <subtitle>ProtoTest Blog</subtitle>
    <icon>https://prototest.dev/img/favicon.svg</icon>
    <entry>
        <title type="html"><![CDATA[Why nobody trusts their integration tests]]></title>
        <id>https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/</id>
        <link href="https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/"/>
        <updated>2026-10-02T18:35:00.000Z</updated>
        <summary type="html"><![CDATA[Integration tests catch the bugs that matter, and still end up ignored. Three complaints explain why, and what changes when the framework owns the hard parts.]]></summary>
        <content type="html"><![CDATA[<p>Ask a .NET team about their unit tests and you hear numbers: coverage, speed, how many. Ask about their integration
tests and you hear complaints. Yet the integration tests are the ones that catch the bugs that matter: the API that
writes something the UI never shows, the message that arrives twice, the job that runs at the wrong time.</p>
<p>I kept hearing the same three complaints, and I kept having them myself. ProtoTest is my answer to all three.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="every-test-is-mostly-setup">"Every test is mostly setup"<a href="https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/#every-test-is-mostly-setup" class="hash-link" aria-label="Direct link to &quot;Every test is mostly setup&quot;" title="Direct link to &quot;Every test is mostly setup&quot;" translate="no">​</a></h2>
<p>An integration test needs a running application, a database with the right data, a signed-in user, clients with
the right addresses, and a cleanup that runs even when the test fails. Written by hand, that is dozens of lines
before the first line of behaviour. Copy it into the next test, and the next, and the suite becomes a pile of
setup with a few assertions hidden inside.</p>
<p>In ProtoTest the suite does that work once. A test asks for what it needs with attributes, and the body is only the
behaviour:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:var(--code-text);--prism-background-color:var(--surface-sunken)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:var(--code-text);background-color:var(--surface-sunken)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:var(--code-text)"><span class="token punctuation" style="color:var(--code-punctuation)">[</span><span class="token attribute class-name" style="color:var(--code-type)">Application</span><span class="token attribute attribute-arguments punctuation" style="color:var(--code-punctuation)">(</span><span class="token attribute attribute-arguments string" style="color:var(--code-string)">"Api"</span><span class="token attribute attribute-arguments punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">[</span><span class="token attribute class-name" style="color:var(--code-type)">ProtoTest</span><span class="token punctuation" style="color:var(--code-punctuation)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">[</span><span class="token attribute class-name" style="color:var(--code-type)">SignedInAs</span><span class="token punctuation" style="color:var(--code-punctuation)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token keyword" style="color:var(--code-keyword)">public</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">async</span><span class="token plain"> </span><span class="token return-type class-name" style="color:var(--code-type)">Task</span><span class="token plain"> </span><span class="token function" style="color:var(--code-function)">RestWritesAreVisibleThroughGraphQL</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">    </span><span class="token keyword" style="color:var(--code-keyword)">using</span><span class="token plain"> </span><span class="token class-name keyword" style="color:var(--code-keyword)">var</span><span class="token plain"> created </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">await</span><span class="token plain"> Proto</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Context</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">Rest</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">Body</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token keyword" style="color:var(--code-keyword)">new</span><span class="token plain"> </span><span class="token constructor-invocation class-name" style="color:var(--code-type)">CreateProjectRequest</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token string" style="color:var(--code-string)">"atlas"</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">PostAsync</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token string" style="color:var(--code-string)">"/api/v1/projects"</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">    created</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Should</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">HaveHttpStatus</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token plain">HttpStatusCode</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Created</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">    </span><span class="token keyword" style="color:var(--code-keyword)">using</span><span class="token plain"> </span><span class="token class-name keyword" style="color:var(--code-keyword)">var</span><span class="token plain"> projects </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">await</span><span class="token plain"> Proto</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Context</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">GraphQL</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">Query</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token string" style="color:var(--code-string)">"projects"</span><span class="token punctuation" style="color:var(--code-punctuation)">,</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">new</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">{</span><span class="token plain"> first </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token number" style="color:var(--code-number)">10</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">}</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">ExpectAsync</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token keyword" style="color:var(--code-keyword)">new</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">{</span><span class="token plain"> totalCount </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token number" style="color:var(--code-number)">1</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">}</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">}</span><br></div></code></pre></div></div>
<p>The application starts once for the suite. The user is created for this test and removed after it. The clients
know their addresses. Written as a regular NUnit fixture against the same application, a comparable test carries 35
lines of plumbing; with ProtoTest it carries 13. The <a class="" href="https://prototest.dev/docs/project/compare/">side-by-side comparison</a> marks every
one.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="it-fails-sometimes-and-nobody-knows-why">"It fails sometimes, and nobody knows why"<a href="https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/#it-fails-sometimes-and-nobody-knows-why" class="hash-link" aria-label="Direct link to &quot;It fails sometimes, and nobody knows why&quot;" title="Direct link to &quot;It fails sometimes, and nobody knows why&quot;" translate="no">​</a></h2>
<p>The flaky test. It passes on your machine and fails on CI, or fails on Monday and passes on retry. After a few weeks
people stop reading its failures, and then it stops protecting anything.</p>
<p>Almost every flaky integration test comes down to something the test did not own:</p>
<ul>
<li class=""><strong>Time.</strong> The test waits a real second and hopes the job has run. On a busy machine it has not.</li>
<li class=""><strong>State.</strong> The test reads a record an earlier test created. In a different order, it is not there.</li>
<li class=""><strong>Addresses.</strong> The test talks to <code>localhost:5099</code> because that is where it ran on one machine.</li>
</ul>
<p>ProtoTest gives each test its own. Every test gets its own clock, which the application reads and the test moves:
<code>Proto.Context.Clock.Advance(TimeSpan.FromDays(8))</code> instead of a <code>Task.Delay</code>. State comes from attributes that
create it before the test and remove it after, even on failure, so tests can run in parallel. Addresses come from
one place, the suite's host, so the same test runs against an in-process API, a container or a staging URL without
a change.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="when-it-fails-i-spend-an-hour-finding-out-why">"When it fails, I spend an hour finding out why"<a href="https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/#when-it-fails-i-spend-an-hour-finding-out-why" class="hash-link" aria-label="Direct link to &quot;When it fails, I spend an hour finding out why&quot;" title="Direct link to &quot;When it fails, I spend an hour finding out why&quot;" translate="no">​</a></h2>
<p>A test expects <code>201 Created</code> and gets <code>400</code>. The assertion says exactly that, and nothing else. The response body
that explained the problem is gone. So you rerun it with logging, set a breakpoint, and reconstruct what happened
before the assertion.</p>
<p>In ProtoTest every run records that for you. The trace holds each request and response, each check with what it
expected and what it got, the state the test saw and the files it produced, in the order they happened. A failed
check carries the response body in its message. The viewer and the HTML report open on what needs attention: what
failed, and why. You read the cause before you open the code.</p>
<p><a href="https://trace.prototest.dev/?demo=1" target="_blank" rel="noopener noreferrer" class="">Open a sample run in the viewer</a> to see what that looks like.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-same-three-across-every-boundary">The same three, across every boundary<a href="https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/#the-same-three-across-every-boundary" class="hash-link" aria-label="Direct link to The same three, across every boundary" title="Direct link to The same three, across every boundary" translate="no">​</a></h2>
<p>These complaints get worse as a test crosses more boundaries: an API, a message broker, a browser, a device on a
WebSocket. More setup, more things to own, more places for a failure to hide. ProtoTest keeps one model for all of
them. REST, GraphQL, gRPC, SQL, messaging, browsers and devices use the same attributes, the same clock, the same
host and one trace for the whole run.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="and-now-that-agents-write-tests">And now that agents write tests<a href="https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/#and-now-that-agents-write-tests" class="hash-link" aria-label="Direct link to And now that agents write tests" title="Direct link to And now that agents write tests" translate="no">​</a></h2>
<p>More tests are now written and fixed by coding agents. That makes the third complaint more urgent: a green run
proves less than it seems, because a check can be loosened or a sleep can hide a race. ProtoTest reads the same
trace to judge an agent's work, and only calls a fix proven when the test failed before, passes now and nothing
else broke. That part is new in 1.1, and in preview.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="try-it">Try it<a href="https://prototest.dev/blog/why-nobody-trusts-their-integration-tests/#try-it" class="hash-link" aria-label="Direct link to Try it" title="Direct link to Try it" translate="no">​</a></h2>
<p>The template creates a suite with a first test. Run it and open the trace it writes:</p>
<div class="box_zcA_" data-surface="blueprint"><div class="head_y0od"><span class="title_iwRb">A first suite in a minute</span><button type="button" class="copy_hSYO">Copy commands</button><span class="status_O17g" role="status"></span></div><pre class="commands_jmiW" tabindex="0" aria-label="Starter commands"><code>dotnet new install ProtoTest.Templates
dotnet new prototest -n Shop
cd Shop &amp;&amp; dotnet test</code></pre></div>
<p>The <a class="" href="https://prototest.dev/learn/">Learn track</a> goes from that first test to a suite you trust. And if you want to know why 1.1 exists
at all, I wrote about <a class="" href="https://prototest.dev/blog/why-1-0-shipped-too-early/">why 1.0 shipped too early</a>.</p>]]></content>
        <author>
            <name>Matthias Seys</name>
            <uri>https://github.com/MSeys</uri>
        </author>
        <category label="integration-testing" term="integration-testing"/>
        <category label="dotnet" term="dotnet"/>
        <category label="testing" term="testing"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why ProtoTest 1.0 shipped too early]]></title>
        <id>https://prototest.dev/blog/why-1-0-shipped-too-early/</id>
        <link href="https://prototest.dev/blog/why-1-0-shipped-too-early/"/>
        <updated>2026-10-02T18:30:00.000Z</updated>
        <summary type="html"><![CDATA[What was wrong with 1.0, what I changed for 1.1, and the rules I follow now.]]></summary>
        <content type="html"><![CDATA[<p>A few weeks after I released ProtoTest 1.0, I deleted the LinkedIn post that announced it. Nothing had crashed. No
one had complained. I deleted it because, reading my own docs again, I no longer agreed with them.</p>
<p>This post is about what was wrong, what I changed for 1.1, and the rules I work by now.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-10-got-right">What 1.0 got right<a href="https://prototest.dev/blog/why-1-0-shipped-too-early/#what-10-got-right" class="hash-link" aria-label="Direct link to What 1.0 got right" title="Direct link to What 1.0 got right" translate="no">​</a></h2>
<p>The idea held up. Most of an integration test is not the test: it is starting the application, seeding data,
creating clients, finding addresses, cleaning up and collecting diagnostics. ProtoTest owns that part. A test asks
for what it needs through attributes and gets a context with everything wired, so the body is only the behaviour.</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:var(--code-text);--prism-background-color:var(--surface-sunken)"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:var(--code-text);background-color:var(--surface-sunken)"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:var(--code-text)"><span class="token punctuation" style="color:var(--code-punctuation)">[</span><span class="token attribute class-name" style="color:var(--code-type)">Application</span><span class="token attribute attribute-arguments punctuation" style="color:var(--code-punctuation)">(</span><span class="token attribute attribute-arguments string" style="color:var(--code-string)">"Api"</span><span class="token attribute attribute-arguments punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">[</span><span class="token attribute class-name" style="color:var(--code-type)">ProtoTest</span><span class="token punctuation" style="color:var(--code-punctuation)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">[</span><span class="token attribute class-name" style="color:var(--code-type)">SignedInAs</span><span class="token punctuation" style="color:var(--code-punctuation)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token keyword" style="color:var(--code-keyword)">public</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">async</span><span class="token plain"> </span><span class="token return-type class-name" style="color:var(--code-type)">Task</span><span class="token plain"> </span><span class="token function" style="color:var(--code-function)">RestWritesAreVisibleThroughGraphQL</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">    </span><span class="token keyword" style="color:var(--code-keyword)">using</span><span class="token plain"> </span><span class="token class-name keyword" style="color:var(--code-keyword)">var</span><span class="token plain"> created </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">await</span><span class="token plain"> Proto</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Context</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">Rest</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">Body</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token keyword" style="color:var(--code-keyword)">new</span><span class="token plain"> </span><span class="token constructor-invocation class-name" style="color:var(--code-type)">CreateProjectRequest</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token string" style="color:var(--code-string)">"atlas"</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">PostAsync</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token string" style="color:var(--code-string)">"/api/v1/projects"</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">    created</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Should</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">HaveHttpStatus</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token plain">HttpStatusCode</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Created</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">    </span><span class="token keyword" style="color:var(--code-keyword)">using</span><span class="token plain"> </span><span class="token class-name keyword" style="color:var(--code-keyword)">var</span><span class="token plain"> projects </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">await</span><span class="token plain"> Proto</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token plain">Context</span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">GraphQL</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">Query</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token string" style="color:var(--code-string)">"projects"</span><span class="token punctuation" style="color:var(--code-punctuation)">,</span><span class="token plain"> </span><span class="token keyword" style="color:var(--code-keyword)">new</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">{</span><span class="token plain"> first </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token number" style="color:var(--code-number)">10</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">}</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain">        </span><span class="token punctuation" style="color:var(--code-punctuation)">.</span><span class="token function" style="color:var(--code-function)">ExpectAsync</span><span class="token punctuation" style="color:var(--code-punctuation)">(</span><span class="token keyword" style="color:var(--code-keyword)">new</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">{</span><span class="token plain"> totalCount </span><span class="token operator" style="color:var(--code-punctuation)">=</span><span class="token plain"> </span><span class="token number" style="color:var(--code-number)">1</span><span class="token plain"> </span><span class="token punctuation" style="color:var(--code-punctuation)">}</span><span class="token punctuation" style="color:var(--code-punctuation)">)</span><span class="token punctuation" style="color:var(--code-punctuation)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:var(--code-text)"><span class="token plain"></span><span class="token punctuation" style="color:var(--code-punctuation)">}</span><br></div></code></pre></div></div>
<p>Written as a regular NUnit fixture against the same application, a comparable test carries 35 lines of plumbing.
With ProtoTest it carries 13. The <a class="" href="https://prototest.dev/docs/project/compare/">side-by-side comparison</a> marks every one of them.</p>
<p>Every run also wrote a trace: the requests, the checks, the state, the files. That foundation is still the
foundation in 1.1.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-was-wrong">What was wrong<a href="https://prototest.dev/blog/why-1-0-shipped-too-early/#what-was-wrong" class="hash-link" aria-label="Direct link to What was wrong" title="Direct link to What was wrong" translate="no">​</a></h2>
<p>Three things, and I was the one who caused all of them.</p>
<p><strong>The docs promised more than the code did.</strong> Some pages described behaviour as if it shipped, when it was planned
or only half there. For a testing tool that is the worst possible mistake. You choose a test framework because you
trust it to tell you the truth about your code. If its own documentation is not true, why would you trust its
results?</p>
<p><strong>Every integration felt a little different.</strong> REST, gRPC, messaging, SQL and the browser had grown one at a time,
and it showed. Options were registered in different ways. Waits were written in different ways. You learned
ProtoTest once per package instead of once.</p>
<p><strong>The evidence was there, but you had to dig for it.</strong> The trace recorded everything, which is not the same as
explaining anything. A failing run gave you a lot of data and very little answer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-i-did-about-it">What I did about it<a href="https://prototest.dev/blog/why-1-0-shipped-too-early/#what-i-did-about-it" class="hash-link" aria-label="Direct link to What I did about it" title="Direct link to What I did about it" translate="no">​</a></h2>
<p>I spent the last weeks on nothing else. Not on new ideas, but on making what existed true.</p>
<p><strong>Every docs page was checked against the code.</strong> Where the code did less than the page, either the code changed
or the page did. The integration pages have a Limits section that says what they do not do. A Learn
track in seven parts walks from a first test to extending the framework.</p>
<p><strong>One model for every package.</strong> There is one way to register options, one way to wait, one way to declare a
capability and one way to register infrastructure. A capability is only declared by something that can serve it:
when an integration has no address, it goes inert and its capability is absent. A test marked with
<code>[RequiresCapability]</code> is then skipped with the reason instead of failing on a connection error.</p>
<p><strong>Evidence that leads with the answer.</strong> The viewer and the HTML report now open on what needs attention: what
failed, the rule that explains it, the mismatches. You read the cause before you open a single test.</p>
<p>Doing this meant breaking things. 1.1 removes and renames some 1.0 API, and I am not hiding that: the
<a class="" href="https://prototest.dev/docs/getting-started/migrating-from-1-0/">migration guide</a> lists every change and what to use instead. From 1.1
on, the published 1.x surface only grows. Nothing you use will be removed in a 1.x release.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-rules-i-follow-now">The rules I follow now<a href="https://prototest.dev/blog/why-1-0-shipped-too-early/#the-rules-i-follow-now" class="hash-link" aria-label="Direct link to The rules I follow now" title="Direct link to The rules I follow now" translate="no">​</a></h2>
<p>These are written down in the repository, and every change goes through them:</p>
<ol>
<li class=""><strong>Honest capabilities, honest docs.</strong> Never advertise what does not exist, not in the docs, the README or a post
like this one.</li>
<li class=""><strong>One behaviour change, one test.</strong> Including the failure paths: a missing address, a timeout, a cancellation, a
teardown that fails.</li>
<li class=""><strong>Evidence or it did not happen.</strong> A change is done when the full gate is green: tests, lint and the docs check.</li>
<li class=""><strong>Never a second mechanism.</strong> If the framework already owns something, extend it. Do not build a parallel one.</li>
</ol>
<p>The second rule paid off while I was preparing this release. The Windows build on CI kept failing on a test that
always passed on my machine. The cause was a real bug in the shared polling loop: when less than a millisecond was
left before a deadline, the wait rounded down to zero and the loop spun until the deadline passed. Windows timers
are coarse enough to hit that window. It is fixed, with a test that failed before the fix.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-11-adds">What 1.1 adds<a href="https://prototest.dev/blog/why-1-0-shipped-too-early/#what-11-adds" class="hash-link" aria-label="Direct link to What 1.1 adds" title="Direct link to What 1.1 adds" translate="no">​</a></h2>
<p>With the foundation in order, 1.1 also adds new things:</p>
<ul>
<li class=""><strong>Devices</strong> over WebSocket, MQTT, TCP and serial lines, in the same test as your API and browser.</li>
<li class=""><strong>Aspire, WireMock, Testcontainers and hosted workers</strong> as parts of the same run.</li>
<li class=""><strong>An agent layer, in preview</strong>: an MCP server and a CLI that read the trace, compare two runs, and decide whether
a fix is proven. The <a class="" href="https://prototest.dev/docs/agent-workflows/coding-agents/">coding agents guide</a> shows the loop.</li>
</ul>
<p>46 packages, one version, one model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="try-it">Try it<a href="https://prototest.dev/blog/why-1-0-shipped-too-early/#try-it" class="hash-link" aria-label="Direct link to Try it" title="Direct link to Try it" translate="no">​</a></h2>
<div class="box_zcA_" data-surface="blueprint"><div class="head_y0od"><span class="title_iwRb">A first suite in a minute</span><button type="button" class="copy_hSYO">Copy commands</button><span class="status_O17g" role="status"></span></div><pre class="commands_jmiW" tabindex="0" aria-label="Starter commands"><code>dotnet new install ProtoTest.Templates
dotnet new prototest -n Shop
cd Shop &amp;&amp; dotnet test</code></pre></div>
<p>If you tried 1.0 and walked away, I understand. I would ask you to look again. And if something in 1.1 does not
do what the docs say, that is a bug, and I want to hear about it on
<a href="https://github.com/MSeys/ProtoTest/issues" target="_blank" rel="noopener noreferrer" class="">GitHub</a>.</p>]]></content>
        <author>
            <name>Matthias Seys</name>
            <uri>https://github.com/MSeys</uri>
        </author>
        <category label="prototest" term="prototest"/>
        <category label="release" term="release"/>
        <category label="open-source" term="open-source"/>
    </entry>
</feed>