I used Kiro to write all my React Tests — Here’s what I learned
When I first learned how to program, I decided to pick up React as it was the most popular framework at the time. As a programmer, I knew…
I used Kiro to write all my React Tests — Here’s what I learned

When I first learned how to program, I decided to pick up React as it was the most popular framework at the time. As a programmer, I knew that testing was important however, I had to prioritise delivering features/projects on time, so that was usually deprioritized. Every developer knows they should write code to test React components, but how does that look like today? What if you could generate comprehensive, high-quality tests in minutes instead of hours?
I recently used Kiro, an AI-powered IDE, to write Jest and React Testing Library (RTL) tests for a component that had zero test coverage. In this post, I’ll walk through the entire process — from generating initial tests, to requesting edge cases, to evaluating and refining the output.
The Starting Point: A Simple Component With No Tests
Let’s start with a straightforward SearchInput component — the kind you’d find in any React app. It handles user input, debounced search, and displays a loading state:
//SearchInput.tsx
import { useState, useEffect } from 'react';
interface SearchInputProps {
onSearch: (query: string) => void;
placeholder?: string;
debounceMs?: number;
isLoading?: boolean;
}
export function SearchInput({
onSearch,
placeholder = 'Search…',
debounceMs = 300,
isLoading = false,
}: SearchInputProps) {
const [query, setQuery] = useState('');
useEffect(() => {
const timer = setTimeout(() => {
if (query.trim()) {
onSearch(query.trim());
}
}, debounceMs);
return () => clearTimeout(timer);
}, [query, debounceMs, onSearch]);
const handleClear = () => {
setQuery('');
onSearch('');
};
return (
<div className="search-input-wrapper">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={placeholder}
aria-label="Search"
disabled={isLoading}
/>
{query && (
<button onClick={handleClear} aria-label="Clear search">X
</button>
)}
{isLoading && <span role="status">Loading…</span>}
</div>
);}
No tests. No safety net. Let’s fix that.
Step 1: Asking Kiro to Generate Initial Tests
I opened the component in Kiro and asked:
“Write Jest and React Testing Library tests for this SearchInput component. Include render tests, interaction tests, and a snapshot test.”
Within seconds, Kiro generated a comprehensive test file:
// SearchInput.test.tsx
import { render, screen, waitFor } from ‘@testing-library/react’;
import userEvent from ‘@testing-library/user-event’;
import { SearchInput } from ‘./SearchInput’;
describe(‘SearchInput’, () => {
const mockOnSearch = jest.fn();
beforeEach(() => {
jest.useFakeTimers();
mockOnSearch.mockClear();
});
afterEach(() => {
jest.useRealTimers();
});
// — — Render Tests — -
describe(‘rendering’, () => {
it(‘renders with default placeholder’, () => {
render(<SearchInput onSearch={mockOnSearch} />);
expect(screen.getByPlaceholderText(‘Search…’)).toBeInTheDocument();
});
it(‘renders with custom placeholder’, () => {
render(<SearchInput onSearch={mockOnSearch} placeholder=”Find users…” />);
expect(screen.getByPlaceholderText(‘Find users…’)).toBeInTheDocument();
});
it(‘does not render clear button when input is empty’, () => {
render(<SearchInput onSearch={mockOnSearch} />);
expect(screen.queryByLabelText(‘Clear search’)).not.toBeInTheDocument();
});
it(‘renders loading indicator when isLoading is true’, () => {
render(<SearchInput onSearch={mockOnSearch} isLoading />);
expect(screen.getByRole(‘status’)).toHaveTextContent(‘Loading…’);
});
it(‘disables input when loading’, () => {
render(<SearchInput onSearch={mockOnSearch} isLoading />);
expect(screen.getByLabelText(‘Search’)).toBeDisabled();
});
});
// — — User Interaction Tests — -
describe(‘user interactions’, () => {
it(‘updates input value when user types’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} />);
await user.type(screen.getByLabelText(‘Search’), ‘hello’);
expect(screen.getByLabelText(‘Search’)).toHaveValue(‘hello’);
});
it(‘calls onSearch after debounce delay’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} debounceMs={300} />);
await user.type(screen.getByLabelText(‘Search’), ‘react’);
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(mockOnSearch).toHaveBeenCalledWith(‘react’);
});
});
it(‘shows clear button when input has value’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} />);
await user.type(screen.getByLabelText(‘Search’), ‘test’);
expect(screen.getByLabelText(‘Clear search’)).toBeInTheDocument();
});
it(‘clears input and calls onSearch with empty string on clear’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} />);
await user.type(screen.getByLabelText(‘Search’), ‘test’);
await user.click(screen.getByLabelText(‘Clear search’));
expect(screen.getByLabelText(‘Search’)).toHaveValue(‘’);
expect(mockOnSearch).toHaveBeenCalledWith(‘’);
});
});
// — — Snapshot Test — -
describe(‘snapshots’, () => {
it(‘matches snapshot in default state’, () => {
const { container } = render(<SearchInput onSearch={mockOnSearch} />);
expect(container.firstChild).toMatchSnapshot();
});
it(‘matches snapshot in loading state’, () => {
const { container } = render(<SearchInput onSearch={mockOnSearch} isLoading />);
expect(container.firstChild).toMatchSnapshot();
});
});
});
This is genuinely solid out of the box. Kiro picked up on the accessibility attributes (aria-label), correctly set up fake timers for the debounce logic, and used userEvent over fireEvent — which is the modern best practice.
Step 2: Requesting Tests for Specific Behaviors
The generated tests covered the happy paths well, but I wanted coverage for some of the edge cases. I asked Kiro:
“Add tests for these edge cases: whitespace-only input should not trigger search, rapid typing should only trigger one search call after debounce, and the component should clean up timers on unmount.”
Kiro immediately generated the following test suite:
describe(‘edge cases’, () => {
it(‘does not call onSearch for whitespace-only input’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} />);
await user.type(screen.getByLabelText(‘Search’), ‘ ‘);
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(mockOnSearch).not.toHaveBeenCalled();
});
});
it(‘only triggers one search for rapid typing’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} debounceMs={300} />);
await user.type(screen.getByLabelText(‘Search’), ‘r’);
jest.advanceTimersByTime(100);
await user.type(screen.getByLabelText(‘Search’), ‘e’);
jest.advanceTimersByTime(100);
await user.type(screen.getByLabelText(‘Search’), ‘a’);
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(mockOnSearch).toHaveBeenCalledTimes(1);
expect(mockOnSearch).toHaveBeenCalledWith(‘rea’);
});
});
it(‘cleans up timer on unmount’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
const { unmount } = render(<SearchInput onSearch={mockOnSearch} debounceMs={300} />);
await user.type(screen.getByLabelText(‘Search’), ‘test’);
unmount();
jest.advanceTimersByTime(300);
expect(mockOnSearch).not.toHaveBeenCalled();
});
});
This is exactly what I would have written manually. Kiro did this in 10 seconds, rather than me taking the time to write this all out, which would’ve taken more than 10 minutes.
Step 3: Evaluating the Quality
Let’s look at what Kiro got right and what places I had to make manual adjustments.
✅ What Kiro Did Well:
· Testing Library best practices: Queries by role and accessible name, not by CSS class or test IDs
· Proper async handling: Used userEvent.setup() with fake timers — this is a subtle pattern that many developers get wrong
· Isolation: Each test is independent with proper setup/teardown
· Readable assertions: Test descriptions clearly communicate intent
🔧 Adjustments I Made:
1. The rapid-typing test needed a small fix. userEvent.type types all characters sequentially, so typing additional characters after the first type call appends to the existing value. The test intent was correct, but I consolidated it:
it(‘debounces rapid input — only the final value triggers search’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} debounceMs={300} />);
const input = screen.getByLabelText(‘Search’);
await user.type(input, ‘react’);
jest.advanceTimersByTime(300);
await waitFor(() => {
expect(mockOnSearch).toHaveBeenCalledTimes(1);
expect(mockOnSearch).toHaveBeenCalledWith(‘react’);
});
});
2. I then added a custom debounce value test to confirm the prop actually works:
it(‘respects custom debounce timing’, async () => {
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
render(<SearchInput onSearch={mockOnSearch} debounceMs={500} />);
await user.type(screen.getByLabelText(‘Search’), ‘test’);
jest.advanceTimersByTime(300);
expect(mockOnSearch).not.toHaveBeenCalled();
jest.advanceTimersByTime(200);
await waitFor(() => {
expect(mockOnSearch).toHaveBeenCalledWith(‘test’);
});
});
These were minor refinements. The overall structure and approach were production-ready from the start.
The Verdict: Is AI-Generated Testing Worth It?
Overall I felt like the AI-generated tests written by Kiro were worth it. Some benefits I felt come through from this as mentioned below:
The Amount of Time saved: What would have taken me 30–45 minutes took under 5 minutes, including review and refinements.
Quality assessment: I would give this 8/10 — the generated tests follow best practices, cover the right scenarios, and are maintainable. The structure and approaches were correct. I just had to make a few adjustments — however they were more about my personal preferences rather than correctness.
Where Kiro shines: Generating the boilerplate and test structure instantly. Kiro being able to correctly identifying accessibility patterns to query by, handling async patterns (e.g., timers, user events) without common pitfalls. Producing tests that actually test behavior, not just implementation details.
Where human review still matters: Validating that edge case tests match real-world scenarios. Catching subtle interaction issues between tests. Deciding which snapshot tests actually provide value (hint: fewer than you think)
Some Tips for Kiro based on my experience
-
Start broad, then go specific. Ask for general test coverage first, then request edge cases.
-
Mention your testing philosophy. If you prefer integration-style tests over unit tests, say so.
-
Point out the tricky parts. If your component has complex async behavior or conditional logic, highlight it.
-
Review timer and async handling carefully. This is where AI tools most commonly need adjustment.
-
Use Kiro iteratively. Generate → Run → Fix → Ask Kiro to handle the failures.
Conclusion
Kiro doesn’t replace your testing expertise. Kiro can help amplify and complement that. Instead of spending mental energy on boilerplate, you can focus on what to test and why. The result is faster tests coverage without sacrificing quality of the feature/product that you’re building.
If you’ve been putting off writing tests because of the time investment, give Kiro a try. Your future self (and your CI pipeline) will thank you.
Have you used AI tools to accelerate your testing workflow? I’d love to hear your experience in the comments.
메타데이터
- post_id
- f6059bf5bbaf
- slug
- i-used-kiro-to-write-all-my-react-tests-heres-what-i-learned-f6059bf5bbaf
- url
- https://medium.com/@rtan265/i-used-kiro-to-write-all-my-react-tests-heres-what-i-learned-f6059bf5bbaf
- canonical_url
- https://medium.com/@rtan265/i-used-kiro-to-write-all-my-react-tests-heres-what-i-learned-f6059bf5bbaf
- author_url
- https://medium.com/@rtan265
- status
- ok
- fetched_at
- 2026-06-09 15:37:30