DEV Community

Cover image for First Component- Answering
jacobwicks
jacobwicks

Posted on

First Component- Answering

Our first component is going to be the screen that displays the card. We will call this component "Answering." We will call it "Answering" because answering questions is what this component lets the user do. This post will show you how to take the user story about this component and turn it into tests. We will then write Answering using Red, Green, Refactor. In the next post we will change the main App component to show Answering on the screen.

The Answering Component

User Story

  • The user sees a question displayed on the screen. The user writes an answer to the question. When the user is done with their answer, they click the submit button. The app shows them the answer key. The user compares their answer to the answer key. The user decides they got the question right, and clicks the 'right answer' button. Then the user sees the next question.

We are going to make this user story happen!

Features

Here are the features that we need to have to make the user story possible.

  • show the user the question from the card
  • a box that the user can type their answer in
  • a button to submit the user's answer
  • show the user the answer from the card
  • a button to record a right answer
  • a button to record a wrong answer

Choose Components

This is the reasoning for which components we are going to use.

We'll put all our components in a Container to keep them organized on the screen.

We need to show the user the question. Let's use a Header to show the question.

We could use an Input to give the user a place to type their answer, but Inputs are usually for a single line of text. Flashcard answers can get pretty long, so we'll use a TextArea. TextAreas are a big box that the user can type in.

We will also use a Form. A Form lets you group inputs together. We need to put the TextArea inside a Form because the Semantic UI React TextArea gets some css styling from being inside a Form. It won't look right without the Form.

The buttons are an easy choice. We'll use a Button for the buttons.

We want to show the answer to the user when they are ready to see it. That means sometimes the answer will be hidden, but sometimes it will be shown. We'll use a Transition component to animate the answer when it appears. We'll put a div with a header inside the Transition. But we won't make the Answer component right now. We'll make the Answer component later, in post 3. To keep this post shorter, we won't write tests for the Answer yet. We'll do that in post 3.

Get Ready to Write the Tests

We are making this app using Test Driven Development. That means the first thing we write when we make a component is the tests. The main reason to write tests first is that it helps to break down the problem you are trying to solve. Other good reasons to write tests first are that you will know when your code works. You will also know when changes make something stop working.

Red

We will write the tests in Typescript. We will write the tests using functions provided by Jest and React Testing Library. We will use Jest to run the tests. We'll write a test and see that it fails. That's good! Each test you write should fail because you haven't written the code to pass it yet. This is the "Red" part of red, green, refactor. It is the red part because a failing test will show up red in the test running program.

Green

Then we'll write the code to pass the test and run the tests again. You want to write the least amount of code that you need to pass the test. Once you have done that, the test will pass. This is the "Green" part of red, green, refactor. It is the green part because a passing test will show up green in the test running program.

Refactor

Once the component passes all the tests we are ready to refactor. Refactoring is when you can improve the code. You can make it more efficient. You can make the code easier to read. You can take out parts of the code that are repeated. Then we are ready to write another component or add more features.

Decide What to Test

In this first set of tests we are going to write a test for everything. Once you get more experienced at testing you won't write a test for everything. You will decide what to test and what not to test.

Deciding what to test is important. You want to test the things that matter. You do not want to test the things that don't matter. What matters and what doesn't is something you will get a feel for as you do more testing.

Think about the features we are implementing. Think about the components we decided to use. We will test for the things that matter. We will test for:

  • the Container that holds everything else
  • the Header that shows the question
  • the Button to skip to the next question
  • the TextArea to write the answer in
  • the Button to submit the answer

Snapshot

We will also put in a snapshot test. The first time you run a snapshot test it records how the component looks. Every time you run the snapshot test after the first time it compares how the component looks now to how the component looked the first time it ran. If it looks different then it fails. Snapshots are useful because they tell you when you changed what shows up on the screen. But you won't want to use snapshot tests in every situation.

Not Showing the Answer Yet

To make this first set of tests easier we aren't going to test for the component that shows the answer yet. We'll do that in Post 7, where we make the separate component that shows the answer.

Writing the Tests

File: src/scenes/Answering/index.test.tsx
Will Match: src/scenes/Answering/complete/test-1.tsx

The folders have been created so you have somewhere to put the files, but I left the files out so that you can write them yourself. Whenever the tutorial has you make a new test file or component, you'll have to create the file yourself and then write the contents.

Create a new test file named index.test.tsx in the folder src/scenes/Answering/. In the test file, write a comment line for each test you are going to write.

//we need
//a container, 
//test to see if the question prompt is in the document
//test to see if the Skip button is in the document
//a textarea to write the answer in
//test to see if the Submit button is in the document
//and the snapshot
Enter fullscreen mode Exit fullscreen mode

Add the imports at the top of the file, above your comments.

//React lets us create and display components to the user
//We need to import it so that we can look at the components to test them
import React from 'react';

//testing library gives us methods to test components
//we use render to look at React components
//we use cleanup to clear out memory after tests
import { render, cleanup } from '@testing-library/react';

//extend-expect gives us methods that let us say what we think a component will look like when we test it
import '@testing-library/jest-dom/extend-expect';

//This is the Answering component that we are going to write
//we have to import it so that we can look at it to test it
import Answering from './index';
Enter fullscreen mode Exit fullscreen mode

Then call afterEach(cleanup)

afterEach(cleanup);
Enter fullscreen mode Exit fullscreen mode

afterEach() is a method from Jest. Just like the name says, afterEach is called after each test.

cleanup is a method from React Testing Library. It clears out memory after a test. Each test is going to render some components. We don't want those components to stay around afterwards because they will take up memory. By passing cleanup to afterEach we make sure that everything gets cleaned up after each test.

Test for Each Feature

The way that you test components using React Testing Library is you use the render() method to render the component. Then you search in the rendered result for the feature that you want to test. Then you make your "assertions" about the feature.

We listed 5 features that we want to see in the Answering component:

  1. Container
  2. Header to show the question
  3. Button to skip the card
  4. TextArea to write the answer in
  5. Submit button

Answering Test 1: Has a Container

File: src/scenes/Answering/index.test.tsx
Will Match: src/scenes/Answering/complete/test-2.tsx

I am going to explain the first test line by line. Later in the tutorial, when you are more familiar with the concepts, we won't go over them in so much detail.

it('has a Container', () => {
    const { getByTestId } = render(<Answering/>);
    const container = getByTestId('container');
    expect(container).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

What this test does is check to see if there is a particular object in the result of the render.

The First Line of Test 1

it('has a Container', () => {
Enter fullscreen mode Exit fullscreen mode

it() is a method provided by Jest. The it() method takes two parameters.

The first parameter is the name of the test. The name is a string. The name of this test is 'has a Container.' The name is what Jest will print on the screen when the test runs, succeeds, or fails.

The second parameter is a function. The function is what will be run when the test is run. The last characters in the first line, () => { are the start of an anonymous function. Look at the last line. See the closing bracket } on the last line? That's the end of the anonymous function. The lines of code in between the {} are what will be run every time this test is run.

Using an anonymous function lets you declare the function inside the call to the it() method instead of having to declare the function somewhere else before passing the function to it().

The Second Line of Test 1

const { getByTestId } = render(<Answering/>);  
Enter fullscreen mode Exit fullscreen mode

< Answering /> may look like HTML, but it is actually using JSX to call to the element returned by the React component named Answering. Don't get too confused- we haven't written the Answering component yet. But once we do, calling it will return some code that will eventually become HTML that can be displayed on the screen. That's what React does!

render() is a function that we get from React Testing Library. You pass a React component to render, and render will do basically the same thing that a web browser does. It will turn the code into HTML. But instead of turning the code into words and pictures on the screen, the render function returns an object. The object that render returns has a lot of functions in it. Most of the functions are designed to let you search through the code that your component turned into. When you search through the code that your component turned into you are testing if the code looks like you think it should.

const is one of the Javascript commands to declare a variable.

One of the methods that render() returns is called getByTestId. I'll explain what it does a little later. We want to give our newly declared const variable the value of render(< Answering />).getByTestId.

When we put the new name of the variable inside the curly brackets { getByTestId } we are telling the compiler three things.

  1. there is an object on the other side of the equals sign.
  2. that object has a property called getByTestId.
  3. we are declaring a variable and giving it the value of the getByTestId property.

We could instead do that like this:

const getByTestId = render(<Answering/>).getByTestId

Doing this with the curly brackets is called Object Destructuring.

const { getByTestId } = render(<Answering/>)
Enter fullscreen mode Exit fullscreen mode

Using Object Destructuring is shorter and easier to read than object.property.

The Third Line of Test 1

const container = getByTestId('container');
Enter fullscreen mode Exit fullscreen mode

getByTestId

In this test we are using the getByTestId method. getByTestId looks through the result of render for the testId that we pass to it. This test is looking for the testId 'container'. If getByTestId finds something with a testId 'container' then getByTestId returns a reference to that object. If getByTestId doesn't find something then it will throw an error.

getByTestId is one query you can use to find things. There are a lot of other queries that you can use to find things. We will use those other queries later in the tutorial.

What are testIds?

Components don't come with testIds. Not all components have testIds. The testId doesn't do anything in the code. It just gives you a way to find a component when you are testing. You can assign a testId to a component when you write the code for the component. Assigning a testId to a component looks like this:

<Answering data-testid='this is the testId I want to give it' />
Enter fullscreen mode Exit fullscreen mode

When we write the Answering component, we will give the Container component a testId 'container'. That way this test will find the Container when we use getByTestId('container').

Looking for the element with a testId 'container' doesn't tell you that the element is a container. It doesn't tell you what's in it. It just tells you that there is (or isn't) an element with a testId 'container.'

The Fourth Line of Test 1

    expect(container).toBeInTheDocument();
Enter fullscreen mode Exit fullscreen mode

expect

Expect is a method provided by the Jest library. We use expect() to look at the element that we pass to it. We use expect to tell us if our assertions about the element are true or not.

Assertions and Matchers

Matchers are how you find objects in the test. Assertions are how you say what you think the result of a test should be. Using Assertions and Matchers together is how you say "I think this element will have this certain value" about the components you are testing.

toBeInTheDocument

One of the methods that expect() gives us is toBeInTheDocument(). The toBeInTheDocument() matcher will pass if the object we gave to expect() is found in the document that we got back from render(). If the object isn't in the document, then the test will fail.

Why use toBeInTheDocument when there are other ways to to test if something exists?

There are a lot of ways to test if an element exists. Here's an example of a message board post asking about what the best method is. They all tell you something specific and different from other methods. The toBeInTheDocument() matcher tells you that the element you are looking for exists and is in the document when you looked for it. Other methods might just tell you that the element is not null, or has a value that is 'truthy'. These can be important things to know. But for our first tests, what we want to know is that the elements are in the document.

Run the First Test- "Red"

We are going to run the first test. It should fail because we haven't written the component it is testing yet. Jest will automatically find the test file when you save it and attempt to run it if you already have Jest running. You might already have Jest running if you started it in the previous post and didn't stop it. This is fine.

If you don't have Jest running already, start it using the command npm test.

Npm Test

You don't have to understand anything in the next paragraph, but I wanted to give you a short explanation of all the steps that happen when you run the command npm test.

This project was started with create-react-app. create-react-app automatically sets up a lot of the stuff that is needed to make a React app work. One of the things that create-react-app sets up is testing. It sets up the scripts so that all you need to do is type 'npm test' and Jest, our test running program, will find the tests and run them. create-react-app also sets up Babel. Babel turns React code into JavaScript. Jest only understands JavaScript, so for Jest to test React components the React code has to go through Babel first.

What happens when you run the command npm test is npm looks in the package.json file in your project directory. It looks inside "scripts" and finds the script named "test." Then npm will run the commands that it finds there. The command that is there by default is "react-scripts test." "react-scripts test" is a set of commands that runs your code through the Babel compiler then tells Jest where to find the tests in your project folder.

Now Run the Test

Type the command npm test and hit enter.

flashcard> npm test
Enter fullscreen mode Exit fullscreen mode

Jest will tell you that the test suite failed. A test suite is what Jest calls a group of tests in a file. This test suite failed because we haven't written the Answering component yet, so it couldn't load it. Once we make Answering then the test suite will have something to load. When we write more tests we will see individual tests failing, not a whole suite.

First Test Suite Fails

This is the "Red" part of Red, Green, Refactor. You want the test to fail when you first write it. If your test doesn't fail when you write it, that means you are testing for something your app already did. That means you are not testing for a new feature.

Jest Commands and "Watch" mode

Jest is the program that runs the tests that we write. We'll be using Jest a lot. So now is a good time to learn about some of the commands that you give Jest. You started Jest when you ran the command "npm start." By default Jest is set up to go into "watch mode." When Jest is in watch mode it will watch for changes that you make to files. When you change a file and save it, Jest will run your tests again. This saves a lot of time because you don't have to switch to the testing window and type "npm test" every time you want to see your new test results.

You may have noticed this menu of Jest commands.

Watch usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.
Enter fullscreen mode Exit fullscreen mode

Typing 'a' will run all tests in the project. Typing 'q' will turn off watch mode. Don't turn off watch mode!
Typing 'p' brings you to a screen where you can run tests from a particular file. Try typing 'p' then type 'A'. See how Jest gives you a selection menu?
Jest Selection
Finish typing 'Answer.' Use the arrow keys to select a file. Hit enter to run the tests.
Jest Selection 2

If you want to quit out of Jest without closing the window, hit control + c. This command may be different on Linux and Macs.

Pass Answering Test 1: Has a Container

File: src/scenes/Answering/index.tsx
Will Match: src/scenes/Answering/complete/index-1.tsx

Create a new index file named index.tsx in the folder src/scenes/Answering/.

//import React so that we can use JSX
import React from 'react';

//import all the components from Semantic UI React
import {
    Button,
    Container,
    Form,
    Header,
    TextArea
} from 'semantic-ui-react'

const Answering = () => {
return (
    <Container data-testid='container' style={{position: 'absolute', left: 200}}>
    </Container>
    )}; 

export default Answering;
Enter fullscreen mode Exit fullscreen mode

The Container. We gave it a testId of 'container' so that the query in the test can find it.

The style prop lets us set CSS on the container. We'll move it slightly away from the left side of the screen. Later in the tutorial we'll add the Selector menu to the left side of the screen, so we'll need space for that.

    <Container data-testid='container' style={{position: 'absolute', left: 200}}>
    </Container>
Enter fullscreen mode Exit fullscreen mode

Run the Test Again

If Jest is still in watch mode then it will run automatically when you save your changes to Answering/index.tsx. If Jest is not in watch mode or you quit out of it, run the command npm test to start it up again.

First Test Passes
Now the test passes! We are ready to add the second test.

Answering Test 2: Has a Question

File: src/scenes/Answering/index.test.tsx
Will Match: src/scenes/Answering/complete/test-3.tsx

Write the second test. The second test gets added to the file that you already started. Do not delete the first test.

//test to see if the question prompt is in the document
it('has a question prompt', () => {
    //Use Object Destructuring to get getByTestId from the result of render
    const { getByTestId } = render(<Answering/>);

    //find question by searching for testId 'question'
    const question = getByTestId('question');

    //assert that question is in the document
    expect(question).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

What's going on in this test?

This test looks a lot like the first test that we wrote. Compare it to the first test. Can you identify the name?

The name of this test is 'has a question prompt.'

Do you see where the function that we pass to it() starts and ends?

Each line in this test is doing the same thing as the same line in the test for the container. The difference is that we call the element variable question, and we search for a testId 'question'. Then we test the assertion that question is in the document.

Question Test Fails: Red

The second test fails when you run it.
Second Test Fails

Look at the top of the command prompt screen where your tests are running. See the HTML that is printed at the top of the screen? That is the HTML that your code turns into. When a test fails, Jest will print the HTML so that you can see what is being rendered. Seeing the HTML gives you a starting point to figure out why your test is failing.

Look at the HTML that Jest printed when this test failed. Can you see the testId of the container? Can you figure out why the test for the question failed?

The test failed because there is no component with a testId 'question' in the HTML that Jest was testing.

Now let's write the code to pass the second test.

Pass Answering Test 2: Has a Question

File: src/scenes/Answering/index.tsx
Will Match: src/scenes/Answering/complete/index-2.tsx

Change the Answering component so it will pass the test looking for a question. Add a Header component. We already imported the Header component from Semantic UI React so you don't need to change the imports for this step. Give the Header component a testId 'question.'

const Answering = () => {
return (
    <Container data-testid='container' style={{position: 'absolute', left: 200}}>
         <Header data-testid='question'/>
    </Container>
    )}; 
Enter fullscreen mode Exit fullscreen mode

This is the Header that will eventually show the question to the user.

<Header data-testid='question'/>
Enter fullscreen mode Exit fullscreen mode

Second Test Passes
Now the test passes! We are ready to add the third test.

Answering Test 3: Skip Button

File: src/scenes/Answering/index.test.tsx
Will Match: src/scenes/Answering/complete/test-4.tsx

We want the user to be able to skip to the next card. We'll give them a Button to do that. We plan to have the button labeled 'Skip.' So we don't have to use getByTestId to find the button. We can search the document to find the text 'Skip.' We search for text using the getByText method.

//test to see if the Skip button is in the document
it('has a button to skip the card', () => {
    //use Object Destructuring to get getByText from the result of render
    const { getByText } = render(<Answering/>);

    //find Skip button by searching for string 'Skip'  
    const skip = getByText('Skip');

    //assert that the Skip button is in the document
    expect(skip).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

getByText is different from getByTestId. getByTestId searches for testIds. getByText searches for text. getByText will return a reference to the element that contains the text. We can find the Skip button by searching for the text string 'Skip' because we will make the Skip button show the string 'Skip' to the user.

The string 'Skip' that we give to getByText has to be capitalized because getByText is case sensitive, and we will capitalize 'Skip' on the Button.

Skip Button Test Fails: Red

Run the test. It will fail because there is no text 'Skip' in the document.
Third Test Fails

Pass Answering Test 3: Skip Button

File: src/scenes/Answering/index.tsx
Will Match: src/scenes/Answering/complete/index-3.tsx

Change the Answering component so it will pass the test looking for the Skip button. Add a Button component. We already imported the Button component from Semantic UI React so you don't need to change the imports for this step. Give the Button content that says 'Skip.'

const Answering = () => {
return (
    <Container data-testid='container' style={{position: 'absolute', left: 200}}>
         <Header data-testid='question'/>
         <Button>Skip</Button>
    </Container>
    )}; 
Enter fullscreen mode Exit fullscreen mode

Third Test Passes

Now the test passes! We are ready to add the fourth test.

Answering Test 4: Has a TextArea

File: src/scenes/Answering/index.test.tsx
Will Match: src/scenes/Answering/complete/test-5.tsx

Here's the test for the TextArea where the user will write their answer. You should recognize every part of it from the other tests we have written.

//a textarea to write the answer in
it('has a textArea to type the answer in', () => {

    const { getByTestId } = render(<Answering />);

    const textArea = getByTestId('textarea');

    expect(textArea).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

Try to answer these questions:

What is the name of the test?
Where does the anonymous function passed to it() start and end?
What component are we rendering?
What matcher method are we using to find the element inside the component?
What are we calling the element that we search for?
What assertion are we making about the element?

Don't worry if you can't answer some of these questions. You'll pick it up by the end of the tutorial.

Skip Button Test Fails: Red

The test will fail.
Fourth Test Fails

Pass Answering Test 4: Has a TextArea

File: src/scenes/Answering/index.tsx
Will Match: src/scenes/Answering/complete/index-4.tsx

const Answering = () => {
return (
    <Container data-testid='container' style={{position: 'absolute', left: 200}}>
        <Header data-testid='question'/>
        <Button>Skip</Button>
        <Form>
            <TextArea data-testid='textarea'/>
        </Form>
    </Container>
    )}; 
Enter fullscreen mode Exit fullscreen mode

Let's add the TextArea. We'll need to put it inside a Form. We need to put the TextArea inside a Form because the Semantic UI React TextArea gets some css styling from being inside a Form. It won't look right without the Form.

    <Form>
        <TextArea data-testid='textarea'/>
    </Form>
Enter fullscreen mode Exit fullscreen mode

This is the TextArea where the user can type their answer. We gave it a testId 'textarea' so that the query in the test can find it.

Fourth Test Passes

Answering Test 5: Has a Submit Button

File: src/scenes/Answering/index.test.tsx
Will Match: src/scenes/Answering/complete/test-6.tsx

We want the user to be able to Submit their answer. We'll give them a Button to do that.

//test to see if the Submit button is in the document
it('has a button to submit the answer', () => {
    //use Object Destructuring to get getByText from the result of render
    const { getByText } = render(<Answering/>);

    //find Submit Button by searching for string 'Submit'  
    const submit = getByText('Submit');

    //assert that the Submit button is in the document
    expect(submit).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

Because the Submit Button will display text, we'll find it using getByText instead of giving it a testId.

Submit Button Test Fails: Red

Alt Text

Write the Component To Pass Submit Button Test: Green

File: src/scenes/Answering/index.tsx
Will Match: src/scenes/Answering/complete/index-5.tsx

This is the Submit button. Eventually we will write code so that clicking this button will make the Answering component show the answer from the current card to the user.

            <Button>Submit</Button>
Enter fullscreen mode Exit fullscreen mode

Add the Submit button to Answering.

const Answering = () => {
return (
    <Container data-testid='container' style={{position: 'absolute', left: 200}}>
         <Header data-testid='question'/>
         <Button>Skip</Button>
         <Form>
            <TextArea data-testid='textarea'/>
        </Form>
        <Button>Submit</Button>
    </Container>
    )}; 
Enter fullscreen mode Exit fullscreen mode

Alt Text

Now the test passes! We are ready to add the last test. The last test will be the snapshot test that we talked about earlier.

The Last Test: Snapshot

File: src/scenes/Answering/index.test.tsx
Will Match: src/scenes/Answering/complete/test-7.tsx

Now let's write the last test. The last test is the snapshot test.

//and the snapshot
it('Matches Snapshot', () => {
    //get the asFragment method so we can look at the component as a DocumentFragment
    const { asFragment } = render(<Answering/>);

    //expect the result of asFragment() to match the snapshot of this component
    expect(asFragment()).toMatchSnapshot(); 
});
Enter fullscreen mode Exit fullscreen mode

What's going on in this test?

You saw render() in the other tests. render() returns an object with a lot of different methods in it. Most of these methods are different ways of searching through the result of render(). Like the first test, we use Object Destructuring to get a method from the render result. Instead of getting getByTestId, in this test we get the asFragment method from the render result.

asFragment() is the function that we got out of render(<Answering) on the second line. asFragment() returns the code as a DocumentFragment. A DocumentFragment is some code that can be turned into HTML. We'll be testing that code to see if it matches what we think it should.

toMatchSnapshot() is a matcher function that checks if the parameter passed to expect matches a snapshot. A snapshot isn't a picture. A snapshot is a stored version of the code that was generated by render(). This is useful for telling you that the component you got back from render() still looks the same way it did before, even if you changed the code inside the component.

You don't have a snapshot of Answering yet. But after you write the component and then run the test, Jest will generate a snapshot. Jest saves the snapshot of a component into a new folder that it makes for keeping snapshots in. If you change the code and the component no longer matches the snapshot, the snapshot test will fail. When a snapshot test fails Jest lets you update the snapshot to match the new code.

Snapshot Passes First Time

Next post: Make Answering Show Up on the Screen

Top comments (0)