Put the Answering Component on Screen
jacobwicks
Posted on January 17, 2020
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;
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();
});
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);
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
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();
});
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.
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;
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'.
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();
});
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:
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();
});
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/>
Now run the test.
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.
Once the app loads into your web browser, it should look like this:
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();
});
Run the Tests
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
.
Posted on January 17, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.