<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Reuvab]]></title><description><![CDATA[Reuvab]]></description><link>https://reuvab.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 31 Oct 2026 18:51:22 GMT</lastBuildDate><atom:link href="https://reuvab.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Test Slop in React: Review the Tests Harder Than the Code]]></title><description><![CDATA[AI writes the code, but tests steer it. Why React devs should review AI-written tests harder than code, from mocked hooks to screenshot baselines.]]></description><link>https://reuvab.hashnode.dev/test-slop-in-react-review-the-tests-harder-than-the-code</link><guid isPermaLink="true">https://reuvab.hashnode.dev/test-slop-in-react-review-the-tests-harder-than-the-code</guid><category><![CDATA[React]]></category><category><![CDATA[Testing]]></category><category><![CDATA[AI]]></category><category><![CDATA[playwright]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Reuven Aboulker]]></dc:creator><pubDate>Tue, 27 Oct 2026 12:05:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ad6dac88d0eaee27f2d7383/b67de994-d7db-40af-9981-ae9106afb1f4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The tests nobody reads</h2>
<p>Most developers I know now follow the same ritual. The AI produces a feature, and we read the diff carefully. We question the abstractions, rename things, and delete the helper it duplicated for the third time.</p>
<p>Then we scroll down to the test file, see a wall of green checkmarks, and approve.</p>
<p>I have done it too. I was building a performance-sensitive UI component for traders, and Copilot wrote the tests alongside it. When I read them properly, I realised they were essentially testing React itself, not my component. They would have stayed green whatever my component did.</p>
<p>Once I started looking, I found test slop everywhere. My favourite: a test asserting that a trade's status was equal to itself.</p>
<p>That moment changed how I think about AI-assisted development. My argument is simple: when AI writes much of the code, the tests matter more than the implementation. They deserve the most careful review, not the least.</p>
<p>I'm not the first to say tests matter more with AI. But almost every example I find uses backend code: a pricing function, a validator, a REST handler. I spend my days in front-end code for trading desks, and there the problem looks different. Test slop hides in mocked hooks, test ids and snapshots. And the visual layer has a trap I have barely seen anyone discuss: agents quietly regenerating screenshot baselines.</p>
<h2>Code is cheap now. Specs are not</h2>
<p>With an AI assistant, I can regenerate a component in minutes. If the implementation is messy, I can throw it away and ask again. The code has become the disposable part of the system.</p>
<p>The tests are what stays. They are the only durable, executable record of what the software is supposed to do.</p>
<p>There is a second, less obvious reason. AI agents work in a loop: write code, run the tests, fix, repeat until green. <strong>The tests are the stopping condition.</strong> They define what "done" means for the agent. A wrong test is not just a weak check; it is a wrong instruction.</p>
<p>I have plenty of testing instructions written for Copilot. But in a big monorepo they often fall out of context, and the agent simply forgets them. A failing test never falls out of context: it is right there in the output, on every run. That makes the tests the most reliable instructions I have, and the most dangerous ones when they are wrong.</p>
<p>Bad tests fail you in two ways:</p>
<ul>
<li><strong>They pass when the feature is broken.</strong> You ship with false confidence.</li>
<li><strong>They fail when the code improves.</strong> Every cleanup breaks them, so the agent "fixes" the refactor by putting the slop back, or by rewriting the test to match. Refactoring becomes impossible.</li>
</ul>
<p>The second one is the trap I care about most. If I want to write feature + tests first and clean up afterwards, the tests must hold still while the code changes underneath them. Tests coupled to the implementation cannot do that.</p>
<h2>What test slop looks like</h2>
<p>AI usually writes tests <em>after</em> it has written the code, with the implementation in its context. So it describes what the code does, not what the user needs. It also optimises for the things it can measure: passing tests and coverage.</p>
<p>The patterns I keep finding in React codebases:</p>
<ul>
<li><strong>Testing React, not the component.</strong> Asserting that a prop renders or a state update applies, which React already guarantees.</li>
<li><strong>Testing internals.</strong> Spying on hooks and counting calls, or mocking so much that the test only proves the mocks talk to each other.</li>
<li><strong>Tautologies.</strong> Assertions that cannot fail, like a trade status checked against itself.</li>
<li><strong>Selecting by implementation.</strong> AG Grid's internal <code>ag-</code> classes, or a <code>data-testid</code> (an attribute added only for tests) on every element. Upgrade the grid and the test breaks, though the user sees no change.</li>
<li><strong>Moving the goalposts.</strong> The agent changes the expectation instead of the code, and the diff looks like a harmless test update. More on this in the visual tests below.</li>
</ul>
<h2>Behaviour over implementation</h2>
<p>Take a simple trading component: a price ticket that shows the live bid for a currency pair. Here is the kind of test an AI will happily write for it:</p>
<pre><code class="language-tsx">vi.mock('./usePriceFeed');

it('subscribes to the feed and renders', () =&gt; {
  vi.mocked(usePriceFeed).mockReturnValue({ bid: 1.0842 });
  render(&lt;PriceTicket symbol="EURUSD" /&gt;);

  expect(usePriceFeed).toHaveBeenCalledWith('EURUSD');
  expect(screen.getByTestId('price-ticket')).toMatchSnapshot();
});
</code></pre>
<p>It looks reasonable, and it is green. But it mocks the very hook that does the work, checks how that hook was called, and snapshots whatever came out. If I replace the hook with a store subscription, the test breaks even though nothing changed for the trader. If the price renders in the wrong place, the snapshot gets updated and nobody notices.</p>
<p>Here is the same component specified by behaviour:</p>
<pre><code class="language-tsx">it('shows the latest bid for its symbol', async () =&gt; {
  const feed = createFakeFeed();
  render(&lt;PriceTicket symbol="EURUSD" /&gt;, { wrapper: withFeed(feed) });

  feed.push({ symbol: 'EURUSD', bid: 1.0842 });

  expect(await screen.findByText('1.0842')).toBeVisible();
});

it('ignores prices for other symbols', async () =&gt; {
  const feed = createFakeFeed();
  render(&lt;PriceTicket symbol="EURUSD" /&gt;, { wrapper: withFeed(feed) });

  feed.push({ symbol: 'GBPUSD', bid: 1.271 });

  expect(screen.queryByText('1.271')).not.toBeInTheDocument();
});
</code></pre>
<p>The differences are small but they change everything:</p>
<ul>
<li><strong>The fake sits at the boundary.</strong> I replace the market data feed, the thing outside my component, and let everything inside it run for real.</li>
<li><strong>The assertions are what the trader sees.</strong> Text on the screen, not function calls.</li>
<li><strong>The test names read like requirements.</strong> Someone who has never seen the code can review them.</li>
</ul>
<p>This is the guiding idea behind React Testing Library itself: the closer a test is to how the software is really used, the more you can trust it. With AI in the loop, that principle stops being a nice-to-have. It is what lets you throw away the implementation and keep the spec.</p>
<h2>The trap nobody talks about: agents and screenshot baselines</h2>
<p>Behaviour tests cannot see everything. In trading UIs a lot of what matters is visual: a price that flashes green or red on a tick, a number that overflows a narrow grid column, a blotter layout that shifts after a dependency upgrade. For that I use Playwright with screenshot comparisons.</p>
<p>Visual tests have their own version of moving the goalposts. When a screenshot diff fails, the fastest way to green is to regenerate the baselines with <code>--update-snapshots</code>. An agent will do that without hesitation, and so will a tired developer at 7pm. From then on, the baseline is simply whatever the code produced.</p>
<p>This happened to me. I asked an agent for a change I did not expect to affect the visual tests at all. The screenshot tests failed, and instead of stopping to ask, the agent updated the baselines. I only noticed because I wasn't expecting any changes on CI, and the diff had them anyway.</p>
<h2>Red-green, with a new division of labour</h2>
<p>I still work red-green. What has changed is who does each step:</p>
<ol>
<li>Red is mine. I write the failing test that describes the behaviour, or I review an AI draft until I would have written it myself. This is where my attention goes.</li>
<li>Green is the AI's. The agent implements until the tests pass. I don't care much how pretty this first version is.</li>
<li>Refactor is shared, and the tests are frozen. The AI and I clean up the code, but no test expectation changes in this step. If one has to, that is a behaviour change, and it goes back to step 1. This is also where I am genuinely unsure, and I would like to hear other views. TDD was always criticised for being slow. AI removes most of the cost of the green and refactor steps, so maybe TDD is finally practical for everyone. Or maybe, with agents generating tests and code together, the discipline of writing the test first is quietly disappearing, and we will only notice when the specs stop meaning anything.</li>
</ol>
<h2>The part you can't delegate</h2>
<p>AI has made code cheap, and that is mostly good news. But it moves the value somewhere else. If the implementation can be regenerated in minutes, the real asset is the precise, human-reviewed description of what the software must do. In a front-end codebase, that description is the test suite.</p>
<p>In practice, two habits do most of the work. I read the tests first in every pull request, to understand what the component is supposed to do; if they can't tell me, that is the first sign of slop. And when a test looks suspicious, I break the code on purpose and run it. If it stays green, it was never testing anything.</p>
<p>So review the tests harder than the code. They are the instructions your AI follows tomorrow.</p>
<p>I am curious how other teams handle this. Do you review AI-written tests differently from AI-written code? Do you still write the red test first, or has AI changed that for you?</p>
]]></content:encoded></item></channel></rss>