Testing React Components

ReacTDD

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:

  1. A component that will render a list of UserCards
  2. A PlayingCard component and another component that will deal random hands of 5 cards
  3. 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!

Resources