Alert Component: Walkthrough
Steps
- Write a test for the
<Alert />
component for a failed action (error):<Alert message="Error!" />
. It should check for a DOM node that contains the "Error!" content and assert the text of the node isError!
. - Write the code to make this test pass. Use the error example above as a guideline for how this should look (you might also want to render a dummy
<Alert />
inside your<AddComponent />
render so you can visualise it). - Write a test for the
<Alert />
component for a successful action:<Alert message="Success!!!" success />
. It should check for a DOM node with the "Success!!!" content and assert the text isSuccess!!!
. - Write the code that makes this test pass.
- Ensure your component is styled so that error messages show up in a red box, and success messages show up in a green box. Test as you go to ensure you haven't broken anything.
- Add some extra initial state to
<AddProperty />
:
- In the
handleAddProperty()
function, set the above state again (we want to clear any error/success messages before each re-submit of the form so they don't persist.) - When your Axios Promise resolves (
.then()
) then set theisSuccess
state to true and set themessage
to a helpful string for the user. - When your Axios Promise rejects (
.catch()
) keep the isSuccess state asfalse
and set themessage
to a helpful string for the user. Note that to cause an error so you can test this, just kill your API process - this will result in a network error. - Render the
<Alert />
inside your form. It should only show if message is truthy. - Write a test for when
message
is not truthy it does not render the component. - Add a snapshot test to all of the three possible
<Alert />
UI scenarios: "error", "success" and "no message".
1 - Write a test for the <Alert />
component for a failed action (error): <Alert message="Error!" />
.
It should check for a DOM node that contains the "Error!" content and assert the text of the node is string Error!
.
If you haven't already got a __tests__
or tests
folder then please go ahead and make one now inside your /src
directory. Inside create a components
directory, and inside create a file Alert.test.js
(so you should have src/tests/components/Alert.test.js
).
Import in the necessary dependencies:
Remember, you aren't making the <Alert />
component yet - you expect this test to fail.
Now create a new test:
Use react-testing-library
to render the component, passing in a message
prop with the message we'll assert against:
Then we are going to search for the DOM node that contains the error. The getByText()
method is made available by invoking render()
and it is going to come useful in looking for DOM nodes that contain certain text. In our case, we want to look for a DOM node that has the text "Error!"
alertMessageNode
now contains markup found on the DOM that has text content which matches the string Error!
. We then use the DOM text property textContent
to write our expectation that the text of that DOM node is indeed "Error!":
Now run your tests.
Note that this test can be written in a DRYer way, by destructuring getByText
from the result of invoking render()
:
And we can use a regex pattern like /Error/
which matches to any string that contains "Error" (regardless of what comes before or after). This does not change the result of the test in any way, just makes our test easier to read. This test written by using destructuring and a regex will then look like the code below - such a pattern is well used in the react-testing-library
documentation!
2 - Write the code to make this test pass.
Use the error example above as a guideline for how this should look (you might also want to render a dummy <Alert />
inside your <AddComponent />
render so you can visualise it).
Ideally what we want to do here is to create the component going solely by the test. When we pass the test, we can modify the aesthetic of our component etc. in knowledge that we have the test to fall back on.
You might feel confident enough to do this step on your own, in which case stop reading and go do that! Otherwise continue.
We know that (for now) this component won't need state so we want to make it a simple function, and that this component will take a prop of message
that will be rendered between opening and closing tags of an HTML element with the classname .alert
. Let's see how that looks:
In a presentational component, React passes one argument to the function - the object props
. Here we destructure props
to get the prop message
, which we then render inside <div>
tags.
3 - Write a test for the <Alert />
component for a successful action: <Alert message="Success!!!" success />
.
It should check for a DOM node with the "Success" content and assert the text contains Success
string.
What is the difference between this test and our other test? It is a success
prop:
When you pass a prop without assigning a value (as with success
above), it's the equivalent of assigning prop={true}
so the above is really short for:
It's just a nice bit of extra syntactic sugar we get with JSX.
4 - Write the code that makes this test pass.
A couple of things have changed here:
- In addition to the
message
prop, we've also destructured asuccess
prop. - We've changed the class name to a JSX expression with a template literal string inside. Inside this template literal we have another JavaScript expression of a ternary. We always have the class name
.alert
, but depending ifsuccess
is truthy or not we add class.alert-success
or.alert-error
.
5 - Ensure your component is styled so that error messages show up in a red box, and success messages show up in a green box. Test as you go to ensure you haven't broken anything.
Please attempt this one on your own. Use the CSS guidance above to assist you.
6 - Add some extra initial state to <AddProperty />
:
Here's the extra state that needs adding:
Add alert
as key to const initialState = { fields: { ... }, alert: { ... } }
And then below the fields
state hook, add a second hook to manage the state of alert:
8 - In the handleAddProperty()
method, set the above state again
We want to clear any error/success messages before each re-submit of the form so they don't persist.
Right after your event.preventDefault()
:
9 - When your Axios Promise resolves (.then()
) then set the isSuccess
state to true and set the message
to a helpful string for the user.
10 - When your Axios Promise rejects (.catch()
) keep the isSuccess
state as false
and set the message
to a helpful string for the user.
Note that to cause an error so you can test this, just kill your API process - this will result in a network error.
11 - Render the <Alert />
inside your form. It should only show if message is truthy.
We've put this inside the <form>
tag for stylistic reasons but position it where you think is best suited for the aesthetic you're going for:
We mention that we should only render <Alert />
if message
is truthy, so how can we handle this logic? We have a couple of options available to us, but before we look into them lets remember that:
a) We only want to render <Alert />
if there is a message
,
b) message
is of type string,
c) Boolean
values for strings are:
Option 1
Handle this logic in the parent component <AddProperty />
by using a conditional expression when we render Alert inside the form. Notice how we need to wrap the <Alert />
component in ()
and the whole expression in {}
.
Option 2
Handle this logic inside the child component <Alert />
. We do not change <Alert />
inside the form and so leave it like:
Instead we can handle the conditional rendering of <Alert/>
by writing an if(){}
just before the return statement inside Alert.js
This reads as if not truthy message return null
. If the whole if
statement evaluates to true
, the component execution stops here and renders null
to React's virtual DOM. If it evaluates to false, it will be bypassed and the component returns normaly. The component should now look like:
Option 1 vs Option 2
Both approaches produce the same results, with the main differences lying on:
-
who controls the render of the component - their parent (option 1) or itself (option 2),
-
readability - many consider option 2 easier to read
-
testing - when using a per component testing strategy option 2 is easier to test
-
reusability - we can use
<Alert />
in other components without having to worry about how to control it. It is enough to pass a "message" prop for it to conditionally render.
12 - Write a test for when message
is not truthy it does not render the component
To write this test we are going to use the "Option 2" above and asFragment()
instead of getByText()
. In react-testing-library
, asFragment()
gives us a nice DOM slice that corresponds to the React component we're testing
We start by writing a new test and destructuring the asFragment()
method from the result of rendering <Alert />
:
Notice how the prop message
passed into <Alert />
is an empty string. We are certain that no markup should be rendered when message=""
, so we are simply going to create a snapshot test based on the result of invoking asFragment()
What do you think the const alert
will contain? We expect no markup to be rendered, in this case an empty <Fragment />
. The best way to assert this is to use the toMatchSnapshot()
from the jest
framework. We can set our expectation like:
The first time the test runs, jest will grab a stringified DOM representation of alert
and save it under a folder _snapshots_
. Snapshots are a great (and quick) way of creating tests that validate markup
Run your tests and head to _snapshots/Alert.test.snap.js
to see what we mean.
Snapshots do not capture visual changes made by CSS but they do capture everything else, including markup and CSS classes used in a component. They are a very useful tool whenever you want to make sure your UI does not change unexpectedly but they shouldn't be the only type of tests we use. More on snapshot testing here.
13 - Add a snapshot test to all of the three possible <Alert />
UI scenarios: "error", "success" and "no message".
For "no message" we added this snapshot on the example before. For "error" we can do this by adding asFragment
to the properties destructured from rendering <Alert />
and then adding an extra expectation:
For "success" you can use the example above and add a snapshot test to the displays a success message
test!