Put the Answering Component on Screen

jacobwicks

jacobwicks

Posted on January 17, 2020

Put the Answering Component on Screen

In the last post you wrote the Answering component. In this post we will change the main App component to show Answering to the user.

Right now, you have the default App.tsx file in the /src/ folder. The code looks like this:

import React from 'react';
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

What it does is show a rotating React logo and a link to reactjs.org. That's great, but it's not what we want. We want it to show the flashcard app. So we need to change the code in App.tsx. But this is test driven development! So first we'll write a test in App.test.tsx, then we will change the code in App.tsx so it passes the test.

Tests for App.tsx

File: src/App.test.tsx
Will Match: src/complete/test-1.tsx

create-react-app gives you one test for App.tsx. The code looks like this:

import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  const { getByText } = render(<App />);
  const linkElement = getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

We don't want to render a link. We want the App component to show the Answering component to the user. So let's write a test for that.

Start by importing the necessary files and calling afterEach. This is just like the tests for the Answering component, except that we are rendering App.

import React from 'react';
import { render, cleanup, getByTestId } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import App from './App';

afterEach(cleanup);
Enter fullscreen mode Exit fullscreen mode

Write comments

Write a comment for each test you are going to do.
All we want the App to do at this step is show Answering. So that's the first test we are going to add. Then we'll add a snapshot test.

//shows the Answering scene
//snapshot
Enter fullscreen mode Exit fullscreen mode

App Test 1: Shows Answering

File: src/App.test.tsx
Will Match: src/complete/test-2.tsx

//shows the Answering component
it('shows the Answering component', () => {

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

  const answering = getByTestId('answering');
  expect(answering).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

This test is the same format as the tests we wrote for the Answering component. Can you identify the name of the test?
How about the function that is passed to it()?
What query method are we using to find the component with the testId 'answering'?

Run the Tests

To run all of the tests, open a command prompt, navigate to the flashcard folder, and run the command npm test. Or if the tests are currently running, type a to run all tests, or type p to open the pattern matcher, find the file you want to run, select it and run it. If Jest is running and you want to stop it, hit ctrl+c.

The test 'shows the Answering component' will fail. This test fails because you haven't changed the App component to show the Answering component. It will pass once you make the changes.

Default App fails Answering Test

Pass App Test 1: Shows Answering

File: src/App.tsx
Will Match: src/complete/app-1.tsx

Now rewrite the App component. Import Answering. Return Answering and give it a testId 'answering' so that the getByTestId query we used in the test will find it.

import React from 'react';
import './App.css';
import Answering from './scenes/Answering';

const App: React.FC = () => 
      <Answering data-testid='answering'/>

export default App;
Enter fullscreen mode Exit fullscreen mode

Run the tests.

What happens? They should pass, right? You imported the Answering component. You passed it a testId 'answering'. And your test is looking for the testId 'answering'.

But OH NO! The test failed!
Changed App Fails Answering Test

What's going on?

Is Jest broken? Nope. Did you make a typo? Probably not (but it never hurts to check!) The best way to figure out what is going on is to take a look at what is getting rendered.

Use debug to Look at the Result of Rendering

As you can see, when a test fails Jest will print out the rendered code. You can scroll up and look at it. But sometimes you will want to see the rendered result without a failing test.

The way to take a look at what is getting rendered is the debug() method. Go into your App.test.tsx file and change the test to this:

//shows the Answering component
it('shows the Answering component', () => {

//get the debug method from the result of render
  const { getByTestId, debug } = render(<App/>);

//call debug
  debug();

//comment out the query and the assertion
  //const answering = getByTestId('answering');
  //expect(answering).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

debug()

debug() is a method that React Testing Library gives us in the object returned from render(). Calling debug prints the rendered code on the screen so you can see what's in it.
We are using Object Destructuring to get the debug method out of render(< App />). Then we are calling debug().

We commented out the declaration of answering and the expect() test for answering because we know they are failing. Now we need to figure out why.

OK, Run the Test Again.

The test will pass because you commented out the part that was failing. Jest does not run the code because it is commented out. When no code in the test fails, then the test passes. The test passes even if the test isn't really testing anything. So all the tests passed. But scroll up and you will see this:

Alt Text

You can see that the Answering component is showing up. There's all the testids that we gave to the components inside Answering. You can see data-testid='container', data-testid='question', and all the others. But what don't you see?

That's right! data-testid='answering' isn't showing up anywhere! That's why the test was failing.

Even though we passed data-testid='answering' to the Answering component, 'answering' didn't show up in the render result. Because 'answering' wasn't there, getByTestId('answering') threw an error and the test failed.

Fixing the Missing testId Problem

The reason that the testId doesn't show up is that the Answering component that we wrote doesn't do anything with the props that it receives. Assigning data-testid worked for all the other components because they are part of the Semantic-UI-React library. They are written to handle props that are passed to them in a way that makes data-testid show up.

We could change the Answering component so that it looks for props, and displays a testId if it receives one. But where would we put the testId? Take a look at Answering. The container component already has a testId. We would have to add another component around the container to have somewhere to put the testId. We could do that, but we shouldn't!

To make data-testid show up we need to have it show up on a component that gets rendered when Answering is shown on the screen. But the outer component, the Container, already has a data-testid assigned to it from the tests that we wrote for Answering. The container can't have two testids. So to make the data-testid from the props show up we would have to add another div outside of Container. Then we could write code to accept data-testId as a prop, and if it got a testId, put the testId on the outer div.

Adding an Outer Div Would be Bad Design

The outer div wouldn't do anything for us except be a place to show the data-testid. That's bad design. If it doesn't actually do anything to make features work, then you don't need or want it in your app!

You should write your tests to show that your components work the way you need them to. Don't add code just to pass a test if that code doesn't do anything to make features work. If you find yourself doing that, it's a sign that you should delete the test instead of adding the code!

Why Don't We Just Look for a testId That is in Answering?

You could look for a testId that is in Answering. They show up, and you could find them and it would pass the test. But that would be bad test design. That would be bad test design because the testIds in Answering don't have anything to do with making features work.

The testIds in Answering are only there to pass the tests in Answering. Later, we might decide to take the testIds out of the Answering. All the features of Answering would still work, but the test for App would stop working. When a test is designed so that can happen, it means that the test is not actually testing any feature that matters to the user!

It is much better to design your tests so that they test based on features that the user sees and uses. That way, your tests will only fail when the features aren't working, not when something that doesn't matter (like a testId) gets changed.

You Shouldn't Always Test Using testIds

Here's the part of the tutorial that gives you one of the difficult pieces of advice about programming. There is no one best way to test if something is displayed on the screen. Sometimes using a testId is a good way to it. In other situations a testId is not the best way to do it.

Ok, Then How Should We Test to Make Sure Answering is Showing Up?

We know the Answering component shows a button that lets the user submit their answer. This button has the text 'Submit'. The button with the text 'Submit' is a feature that actually matters to the user. So let's make the test look for the text 'Submit' in the rendered component.

App Test 1: Second Version

File: src/App.test.tsx
Will Match: src/complete/test-3.tsx

Change the test for showing Answering to this:

//shows the Answering scene
it('shows the Answering scene', () => {
    const { getByText } = render(<App/>);

    //the Answering scene shows the Skip button
    const skip = getByText(/skip/i);

    //if we find the skip button, we know Answering is showing up
    expect(skip).toBeInTheDocument();
  });
Enter fullscreen mode Exit fullscreen mode

Delete the data-testid='answering' from the Answering component in the App. You aren't using it and it didn't show up anyway. This is what the return value of the App looks like without the testId.

const App: React.FC = () => 
      <Answering/>
Enter fullscreen mode Exit fullscreen mode

Now run the test.

Answering Test Passes

There we go! Answering is showing up in the App component.
Hit ctrl+c to stop running the tests. Type npm start to run the app. You should get a message that it has compiled successfully.

Compiled

Once the app loads into your web browser, it should look like this:

The App so far

There's the Answering component! Beautiful, isn't it?

If the app does not load automatically, open a web browser and type the url: localhost:3000. There it is!

But the App Doesn't Do Anything

That's right, the Answering component doesn't do anything- yet! We haven't made it do anything useful. We'll change that in the next post when we make the CardContext provide the cards so Answering has something to show the user! Let's finish off the work on the App component with a snapshot test.

Snapshot Test

File: src/App.test.tsx
Will Match: src/complete/test-4.tsx

This is just like the snapshot test for the Answering component, except that we are rendering App.

it('Matches Snapshot', () => {
  const { asFragment } = render(<App/>);
  expect(asFragment()).toMatchSnapshot(); 
});
Enter fullscreen mode Exit fullscreen mode

Run the Tests

Snapshot Passes

The snapshot test passed because this is the first time it has been run. The first time you run a snapshot test for a component, Jest will create a new snapshot for that component. So when you ran the snapshot test for the App component it created a new snapshot, and it looked like that snapshot so it passed. The snapshot test will fail once you change the code of the App component to display the Answering component. The snapshot test will fail because the App component will look different. When the snapshot test fails because you made changes that you wanted to make, you will update the snapshot by pressing 'u' on the screen that tells you the test failed. If the snapshot fails when you didn't want to change what shows up, you need to fix the changes you made.

Next Post: CardContext

The next post will show you how to make the CardContext. The CardContext is the component that tracks the cards and makes them available to the other components in the App.

💖 💪 🙅 🚩
jacobwicks
jacobwicks

Posted on January 17, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Introduction, Setup, and Overview
react Introduction, Setup, and Overview

January 17, 2020

Saving to LocalStorage
react Saving to LocalStorage

January 17, 2020

First Component- Answering
react First Component- Answering

January 17, 2020

NavBar
react NavBar

January 17, 2020