In part 3, we wrote tests for the
Counter
component and saw a satisfying
FAIL
message.
That failure is a compass showing us what to do next.
Today, we will move to the
Green
step, turning that red light green.
It is fine if the code is a little rough. For now, let's focus on passing the tests.
Step 1: Create a Minimal Rendering Structure ๐
The first error when running our tests was that the component file could not be found.
Let's create the file so it exists.
Create src/components/Counter.tsx.
src/components/Counter.tsx export default function Counter () {
return (
< div >
{ /* ์์ง์ ์๋ฌด ๋ด์ฉ๋ ์๋ ๋น ๊ป๋ฐ๊ธฐ์
๋๋ค */ }
</ div >
);
}
Back in the test file (Counter.test.tsx), the import that was underlined in red can now be resolved.
Step 2: Add the UI Elements (Static Implementation) ๐
Run the tests again.
This time, you will probably see this error:
TestingLibraryElementError: Unable to find an element with the text: /ํ์ฌ ์ซ์: 0/
The test is looking for the text โํ์ฌ ์ซ์: 0โ (โCurrent number: 0โ) and a โ+โ button, but our component is empty.
Set the behavior aside for now and start by hardcoding the visible elements.
src/components/Counter.tsx export default function Counter () {
return (
< div >
< p >ํ์ฌ ์ซ์: 0</ p >
< button type = "button" >+</ button >
</ div >
);
}
This alone passes half of the tests.
RTL
is satisfied that the text โํ์ฌ ์ซ์: 0โ and the โ+โ button are on the screen.
Step 3: Connect State (useState) ๐
One final hurdle remains.
The test expects the number to change to
1
when the button is clicked.
Currently, however, it is fixed at
0
.
To solve this, we need
React
's state management hook,
useState
.
The screen must change in response to a user action: clicking.
Complete the Component ๐
src/components/Counter.tsx import { useState } from 'react' ;
export default function Counter () {
// [์ํ] ์ซ์๋ฅผ ๊ธฐ์ตํ๋ ๋ฉ๋ชจ๋ฆฌ ์์(state)๋ฅผ ๋ง๋ญ๋๋ค. ์ด๊ธฐ๊ฐ์ 0์
๋๋ค.
const [ count , setCount ] = useState ( 0 );
const handleClick = () => {
// [๋์] ๊ธฐ์กด ๊ฐ์ 1์ ๋ํด์ค๋๋ค.
setCount ( prev => prev + 1 );
};
return (
< div >
{ /* ๊ณ ์ ๋ ์ซ์ 0 ๋์ , ๋ณํ๋ ๊ฐ count๋ฅผ ๋ณด์ฌ์ค๋๋ค. */ }
< p >ํ์ฌ ์ซ์: { count } </ p >
{ /* ๋ฒํผ์ ๋๋ฅด๋ฉด handleClick ํจ์๊ฐ ์คํ๋ฉ๋๋ค. */ }
< button type = "button" onClick ={ handleClick } >
+
</ button >
</ div >
);
}
Step 4: Confirm the Tests Pass (Green) ๐
Implementation is complete.
Let's check whether the tests really pass.
Open a terminal and run:
Terminal
Test results
PASS src/components/Counter.test.tsx
Counter ์ปดํฌ๋ํธ ๋์ ํ
์คํธ
โ ์ด๊ธฐ๊ฐ์ 0์ด๋ฉฐ, + ๋ฒํผ์ ๋๋ฅด๋ฉด ์ซ์๊ฐ 1 ์ฆ๊ฐํ๋ค (50 ms)
If you see a green
PASS
, we have succeeded!
We have completed the
Red (failure) -> Green (success)
stages of the TDD cycle.
Review Why the Tests Pass ๐
Let's revisit why our code passes the tests.
Initial state
useState(0) renders โํ์ฌ ์ซ์: 0โ, and getByText finds it.
Button existence
The <button>+<button> tag allows getByRole('button', { name: '+' }) to succeed.
Click behavior
When user.click() runs, the onClick event triggers setCount.
Result verification
The state changes to 1, the screen rerenders, and the text โํ์ฌ ์ซ์: 1โ (โCurrent number: 1โ) is found.
Terminal git clone https://github.com/PROMLEE/my-tdd-app.git
cd my-tdd-app
git checkout part4
Today, we implemented a real component to make failing tests pass.
Minimal implementation
Instead of trying to write perfect code immediately, we first added the elements the tests required: text and a button.
State management
We added useState
only where a value needed to change, completing the dynamic behavior.
Confidence
Passing tests mean that the component fully meets its minimum requirements.
The code is so simple that there may not appear to be much to improve.
What happens when the component becomes more complex?
Next time, in
Part 5: Refactoring Components While Maintaining Tests
, we will explore cleaning up code with tests in place.