As we learned in Advanced TDD with Go, the best way to write a test before code is to think of it as an executable specification.
We're going to write a basic React component to show us a Quote of the Day.
The basic specification is:
- Accepts a piece of text, the quote of the day
- Renders this quote as normal text
- Provides a heading above the quote
We can make some good-practice design decisions:
- The component will be called
<Quote /> - The text will be passed as a property named
text
To write our first test, we capture those decisions into test code.
We're using vitest syntax for this. This syntax is designed to be identical to the Jest test framework, as that is very widely known. Vitest integrates better with vite.
Create a file Quote.spec.jsx with the following content:
import * as React from "react";
import { render } from "@testing-library/react";
describe("Quote component", () => {
it("renders heading text", () => {
render(<Quote />);
});
});We're starting by adding only the Arrange and Act sections of our test.
- We've captured the intent in our messages that the
Quote componentrenders heading text. render(<Quote />);will render our component html into the virtual DOM
We can run this partial test using
npm run -- --testand we'll get a test failure:
The error test confirms that we've asked a <Quote /> component to render, but we do not have that component.
Let's add the React component file, and call it `Quote.jsx':
const Quote = () => {};
export default Quote;We need to import that into our test file:
import * as React from "react";
import { render } from "@testing-library/react";
import Quote from "./Quote";
describe("Quote component", () => {
it("renders heading text", () => {
render(<Quote />);
});
});When we run the test using
npm run -- --testThe test passes. We have a Quote component available.
It's not the world's best component, exactly. We need to make it actually do something.
Following the requirements, our component needs to display some header text.
Let's take the approach of writing a weak test to get us started, then strengthening it later.
The weak version of test will confirm only that the correct text is displayed, nothing more.
Add the following to Quote.spec.jsx:
import * as React from "react";
import { render, screen } from "@testing-library/react";
import Quote from "./Quote";
describe("Quote component", () => {
it("renders heading text", () => {
render(<Quote />);
expect(screen.getByText("Quote of the Day")).toBeInTheDocument();
});
});We run the test using
npm run -- --testand as expected, the test fails, telling us that no such text can be found:
That's simple enought to fix. We add just enough code to the component to make the test pass (and fulfil the slice of specification we are working on).
Add the following return value to Quote.jsx:
const Quote = () => {
return "Quote of the Day";
};
export default Quote;Once we run the test again, it passes.
We've only specified part of what we need to do. There is no the correct text being displayed for our heading. But it isn't a heading.
The screen object allows us to define the kind of heading we want to find. We define it using the ARIA roles for accessibility.
In our test, we say we want to find something displayed with the ARIA role of heading. We would like that to be at level 2.
Let's add this level of detail into our existing test, to strengthen it:
import * as React from "react";
import { render, screen } from "@testing-library/react";
import Quote from "./Quote";
describe("Quote component", () => {
it("renders heading text", () => {
render(<Quote />);
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
"Quote of the Day"
);
});
});We are searching for something an ARIA reader would recognise as a heading, level 2. We then check that our desired heading text is present in that element.
Running the test will fail, as we do not have any such heading yet.
Let's add the heading html into our Component JSX:
const Quote = () => {
return <h2>Quote of the Day</h2>;
};
export default Quote;Run the test. It passes.
Our final piece of work involves displaying the actual quotation itself. We took a design decision to pass in the quote text as a React property, called text.
Let's specify this in a new test, added to our describe block of Quote.spec.jsx:
it("renders quotation passed in as property", () => {
render(<Quote text="Truth eludes power" />);
expect(screen.getByText("Truth eludes power")).toBeInTheDocument();
});We run that test and watch it fail. Ah, the very heartbeat of our misery.
Anyway, cheer up. We can add this React code into Quote.jsx to make it pass:
const Quote = ({ text }) => {
return (
<>
<h2>Quote of the Day</h2>
<p>{text}</p>
</>
);
};
export default Quote;You can find out more about why that exact code is needed over at BJSS Academy React Basics
The test will now pass.
With user interface work, it's always important to actually look at what we're building.
Even using test-first TDD - or perhaps especially when using it - we must check layouts and conformance to visual design rules. TDD will help us develop components that we can trust to work. But the Mark 1 Eyeball, Human remains the final arbiter of taste.
We had an <App /> component generated for us by vite. Let's replace it's content with an example use of our shiny new Quote component.
In file App.jsx, replace the content with this:
import "./App.css";
import Quote from "./Quote";
function App() {
return <Quote text="Man walks into a bar. Ouch. It was an iron bar" />;
}
export default App;We can run the vite server using the command line:
npm run devWe need to follow the instuctions given on the terminal output to find the correct URL for our browser:
Navigating to this URL in Chrome shows us our <Quote /> component in all its glory:
We have successfully test-driven a React component which displays data passed in as props.
Test-first Fetching Data



