DEV Community

Cover image for How to start practicing TDD while creating a reusable component with React
ViriCruz
ViriCruz

Posted on

How to start practicing TDD while creating a reusable component with React

This article will try to teach how to practice test-driven development while at the same time teaching how to test a reusable react component.

Requirements before starting

  • You have installed node.js on your computer
  • Ensure that npx create-react-app app-name works in your terminal since we are going to use this to create the basic setup of the app
  • Install enzyme npm i -D enzyme
  • Install enzyme adapter npm i - D enzyme-adapter-react-16 since we are using version 16 of React
  • Create or edit src/setupTests.js to match the following code
import '@testing-library/jest-dom/extend-expect';
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure({
  adapter: new Adapter()
})

Enter fullscreen mode Exit fullscreen mode

Now that you have done this ensure that you can run the single test that comes with the setup when we created the app.

Run npm test in your terminal and press a to run all the tests, you will see a similar screen like the following:
running first test

Once you have done this, let’s delete the test case in file src/App.test.js to start creating our tests.

What do we want to achieve? For this exercise we want to create a Button component that we can reuse multiple times inside our app, specifically we want to be able to set a different text every time we call the Button component.

Let’s Go!

Ok, first, following the TDD principles, we need to create a failing test before creating any code for the Button component and App component.

Before creating our tests, we are going to create a describe block and inside we are going to add beforeAll() function to create appWrapper before every test run that will keep our code DRY.

let appWrapper
  beforeAll(() => {
    appWrapper = shallow(<App />)
  })

Enter fullscreen mode Exit fullscreen mode

appWrapper contains our App component.

Now go to src/App.test.js and the first test case will ensure that App component renders a Button component that will be our submit button.

it('renders a button component', () => {
    const submitButton = appWrapper.find(SubmitButton)
})

Enter fullscreen mode Exit fullscreen mode

After writing this line we need to run the tests again and we can see that we have a failing test.
first failing test

Let’s move on to make the test green. Following the other step of TDD, we need to write only enough code to make the test green.

Let’s create Button.js in the src folder with the following code

export default() => {}

Enter fullscreen mode Exit fullscreen mode

And create the following jsx on App.js to return the Button component.

import React from 'react';
import SubmitButton from './Button'

function App() {
  return <SubmitButton />
}
export default App;
Enter fullscreen mode Exit fullscreen mode

Also, we need to import the Button component in src/App.test.js

import React from 'react';
import App from './App';
import { shallow } from 'enzyme';
import SubmitButton from './Button'

describe('App', () => {
  let appWrapper
  beforeAll(() => {
    appWrapper = shallow(<App />)
  })

  it('renders a button component', () => {
    const submitButton = appWrapper.find(SubmitButton)
  })
})

Enter fullscreen mode Exit fullscreen mode

Now we have our test in green.
first test green

Let’s finish this test by doing an assertion. We are going to inspect if the App contains one Button component by adding the following code to the first test:

expect(submitButton).toHaveLength(1)

Enter fullscreen mode Exit fullscreen mode

Full test looks like this:

it('renders a button component', () => {
    const submitButton = appWrapper.find(SubmitButton)

    expect(submitButton).toHaveLength(1)
})

Enter fullscreen mode Exit fullscreen mode

Ensure that the test is still green.

So far we’ve made a little bit of TDD by creating a Button component and render it in App component.

Let’s keep practicing!

Our second test is going to inspect if we are passing a prop called value to the Button component

Let’s create a failing test

it('pass value as prop', () => {
    const submitButton = appWrapper.find(SubmitButton)

    expect(submitButton.props().value).toBeDefined()
  })

Enter fullscreen mode Exit fullscreen mode

We are expecting a prop called value to be defined in the Button component but we received an undefined value.

second failing test

Let’s make this test green!
To make the test pass, we need to pass a prop called value in the Button component inside the App component


function App() {
  return <SubmitButton value="" />
}

Enter fullscreen mode Exit fullscreen mode

Now our test is green again, congrats!

second test green

Let’s do another test to keep practicing TDD, remember practice makes perfect!
For this test, we want to inspect if the value prop is equal to the string “Submit” so we are going to create another failing test.

it("has a value prop equal to 'Submit'", () => {
    const submitButton = appWrapper.find(SubmitButton)

    expect(submitButton.props().value).toEqual('Submit')
  })

Enter fullscreen mode Exit fullscreen mode

As you can imagine this test is going to fail
third failing test

We need to refactor the Button component inside the App component to make the test green.


import React from 'react';
import SubmitButton from './Button'

function App() {
  return <SubmitButton value="Submit" />
}
export default App;

Enter fullscreen mode Exit fullscreen mode

YES!, all the three tests are passing now

third test green

If you get here, Well done! you now have a clear idea of what Test-driven development is.
Just to recap:

  • write a failing test, just enough code to make the test fails
  • refactor only the necessary code to make the test green
  • Then again, write a failing test and then refactor to make the test green and so on

And for final touches, as you can see we are repeating const submitButton = appWrapper.find(SubmitButton) across all our test cases, so let’s refactor this to make our code DRY

let appWrapper, submitButton
  beforeAll(() => {
    appWrapper = shallow(<App />)
    submitButton = appWrapper.find(SubmitButton)
  })

Enter fullscreen mode Exit fullscreen mode

Now we can safely delete const submitButton = appWrapper.find(SubmitButton) in every test and our test will still be green.

Full code:

import React from 'react';
import App from './App';
import { shallow } from 'enzyme';
import SubmitButton from './Button'

describe('App', () => {
  let appWrapper, submitButton
  beforeAll(() => {
    appWrapper = shallow(<App />)
    submitButton = appWrapper.find(SubmitButton)
  })

  it('renders a button component', () => {

    expect(submitButton).toHaveLength(1)
  })

  it('pass value as prop', () => {

    expect(submitButton.props().value).toBeDefined()
  })

  it("has a value prop equal to 'Submit'", () => {

    expect(submitButton.props().value).toEqual('Submit')
  })
})

Enter fullscreen mode Exit fullscreen mode

Additional Resources

A great resource that explains in a very clear way how to do TDD:
TDD Live Coding - Test Driven Development Tutorial with React, Jest, and Enzyme

Find me at github.com/Viricruz

Happy Testing!!

Top comments (0)