Introduction
Since React is just built out of JS code, we can actually test it the same way we would test any other component based systems we may be making.
This will be a brief introduction to how to get started with testing in React, though this is a much deeper topic. There is a lot more that you can do with testing in React.
Follow Along
Feel free to follow along here.
First Test
The first step in our TDD process is to write a failing test. Let's do that with a simple UserCard
component.
// UserCard.test.js
import UserCard from './UserCard'
describe('UserCard', () => {
test('renders', () => {
})
})
This is good enough to get us to red because our component doesn't exist yet. This is enough for a compilation error (even though JS doesn't actually have a compile step). So let's Fix it.
Red => Green
This step is as easy as just making the file and exporting something:
import React from 'react'
const UserCard = () => {}
export UserCard
That will be enough to get us back to green. Now it's time to make it fail again!
Green => Red
Now we need to check and make sure our component will render. There are a few things at work here. Let's get started with actually testing.
// UserCard.test.js
import React from 'react'
import ReactDOM from 'react-dom'
import UserCard from './UserCard'
describe('UserCard', () => {
test('renders', () => {
const entry = document.createElement('div')
ReactDOM.render(<UserCard />, entry)
expect(entry.querySelector('.UserCard') instanceof HMTLElement).toBeTruthy()
})
})
Red => Green
So here we're failing because our component doesn't return anything. Let's fix that.
import React from 'react'
const UserCard = () => (<article className="UserCard"></article>)
export default UserCard
Our test is now passing. Let's dive into what this means. This test is fairly simple in nature. All we're checking for at this point is that the component spits out some HTMLElement
that has a class of UserCard
. This is a good first test. Now that we know our component renders, we can start to drive out more complex behavior.
Refactoring
We are in the refactor stage of our process. This is a great time to name things more properly, clean up any repetition, or pair down our code a bit. Since we really haven't written much at this point, there really isn't anything to refactor. So let's skip this for now and try again when we have another passing test. We bring it up now so that it doesn't become a forgotten step. Let's keep moving.
Green => Red
Let's write a new test!
import React from 'react'
import ReactDOM from 'react-dom'
import UserCard from './UserCard'
const user = { firstName: 'John', lastName: 'Doe' }
describe('UserCard', () => {
test('renders', () => {
const entry = document.createElement('div')
ReactDOM.render(<UserCard user={user} />, entry)
expect(entry.querySelector('.UserCard') instanceof HTMLElement).toBeTruthy()
})
test('Displays a users first and last name in an `h3` element', () => {
const entry = document.createElement('div')
ReactDOM.render(<UserCard user={user} />, entry)
expect(entry.querySelector('.UserCard__name').tagName).toMatch('H3')
expect(entry.querySelector('.UserCard__name').textContent).toMatch('John Doe')
})
})
Note that we're adding props to both of our UserCard
components so that we don't get issues in our previous test.
This test is checking that our component renders an h3
element with the class UserCard__name
and the textContent John Doe
. This helps because we now know we can pass props
to our component and they render as we expect.
Red => Green
Now we need to make it pass!
import React from 'react'
const UserCard = ({ user }) => (
<article className="UserCard">
<h3 className="UserCard__name">{`${user.firstName} ${user.lastName}`}</h3>
</article>
)
export default UserCard
This is enough to get our test passing and that's all we're looking to do! Next, let's...
Refactor
Our test class has some redundancy now. This is is exactly what refactoring is for. Let's take a look at our refactored code.
import React from 'react'
import ReactDOM from 'react-dom'
import UserCard from './UserCard'
let entry
const user = { firstName: 'John', lastName: 'Doe' }
describe('UserCard', () => {
beforeEach(() => {
entry = document.createElement('div')
ReactDOM.render(<UserCard user={user} />, entry)
})
test('renders', () => {
expect(entry.querySelector('.UserCard') instanceof HTMLElement).toBeTruthy()
})
test('Displays a users first and last name in an `h3` element', () => {
expect(entry.querySelector('.UserCard__name').tagName).toMatch('H3')
expect(entry.querySelector('.UserCard__name').textContent).toMatch('John Doe')
})
})
Now we're avoiding making a new mock entry point and rendering our UserCard
. That's going to happen every time for us now.
Refactor => Red
Let's write one more test and then you can try more on your own!
import React from 'react'
import ReactDOM from 'react-dom'
import UserCard from './UserCard'
let entry
const user = { firstName: 'John', lastName: 'Doe' }
describe('UserCard', () => {
beforeEach(() => {
entry = document.createElement('div')
ReactDOM.render(<UserCard user={user} />, entry)
})
test('renders', () => {
expect(entry.querySelector('.UserCard') instanceof HTMLElement).toBeTruthy()
})
test('Displays a users first and last name in an `h3` element', () => {
expect(entry.querySelector('.UserCard__name').tagName).toMatch('H3')
expect(entry.querySelector('.UserCard__name').textContent).toMatch('John Doe')
})
test('Displays a users avatar image in an `img` tag', () => {
expect(entry.querySelector('.UserCard__avatar').tagName).toMatch('IMG')
expect(entry.querySelector('.UserCard__avatar').getAttribute('src')).toMatch(user.avatar.src)
expect(entry.querySelector('.UserCard__avatar').getAttribute('alt')).toMatch(user.avatar.alt)
})
})
We're lastly going to assert that we're generating an image for the user avatar. We're failing because we don't currently have that info. Let's change that.
Red => Green
import React from 'react'
const UserCard = ({ user }) => (
<article className="UserCard">
<img className="UserCard__avatar" src={user.avatar.src} alt={user.avatar.alt} />
<h3 className="UserCard__name">{`${user.firstName} ${user.lastName}`}</h3>
</article>
)
export default UserCard
Practice
Time to practice! Pick a new component that you want to make and test drive it! Suggestions:
- A component that will render a list of
UserCard
s - A
PlayingCard
component and another component that will deal random hands of 5 cards - A
DadJokeGenerator
that hits the Dad Joke Generator and renders a new joke any time the user hits a button
These are just some ideas to get you started. Use your imagination!
Conclusion
TDD is an amazing tool that you get better at the more you use it. You should explore testing with any new technology you use. It's a great way to learn just exactly what that tech can do for you!