Published: 03 August 2020
Simple Tests
Let's say we have a simple form component like this:
//myComponent.js
import React, { useState } from "react"
const MyComponent = ({ title, textBody }) => {
const [email, setEmail] = useState("")
const handleChange = e => setEmail(e.target.value)
const handleSubmit = e => {
e.preventDefault()
return email
}
return (
<form onSubmit={e => handleSubmit(e)}>
<h1>{title}</h1>
<p>{textBody}</p>
<label htmlFor="email">Type your email</label>
<input
type="text"
id="email"
onChange={e => handleChange(e)}
value={email}
/>
<button type="submit">Submit</button>
</form>
)
}
export default MyComponent
Let's start writing some basic tests for this:
Each test file will have atleast 4 basic import statements:
//myComponent.test.js
// always need to import React first
import React from "react"
// all methods relating to RTL will be imported here
import { render } from "@testing-library/react"
// jes-dom allows us to have more ways of asserting against the data
import "@testing-library/jest-dom/extend-expect"
//import the component to test
import MyComp from "."
describe("My Component", () => {
it("dummy test", () => {
expect(true).toBe(true)
})
})
Now, we will render our component with some basic props first:
describe("My Component", () => {
it("renders correctly", () => {
const { getByText } = render(
<MyComp title="RTL" textBody="Testing 123455" />
)
})
})
Couple of things to note here:
getBy + Text
option, lets use this to make some assertionsit("renders correctly", () => {
const { getByText } = render(<MyComp title="RTL" textBody="Testing 123455" />)
const customHeading = getByText("RTL")
const customText = getByText("Testing 123455")
expect(customHeading).toBeInTheDocument()
expect(customText).toBeInTheDocument()
})
toBeInTheDocument
is a matcher which is provided by the jest-dom
import we made earlier into our test file.
When using getByText
, you can also use a regular expression just in case if you don't want to type out a long string:
expect(getByText(/testing/i)).toBeInTheDocument()
However, if your text is broken up into child nodes like below:
<p>To get the latest info, <a href="#">click here</a></p>
Using getByText('To get the latest info, click here')
won't work. You will either need to query both elements separately or use a function:
expect(
getByText((content, element) => content.includes("click here"))
).toBeInTheDocument()
// where,
// content --> represents the string encountered in the component that was rendered (use any string methods in JS)
// element --> represents the element that's currently being looped over (use any HTML methods like innerHTML)
Targetting Inputs
To target inputs, we have few different ways:
//get back the input to which label is attached
expect(getByLabelText("Type your email")).toBeInTheDocument()
// can also use placeholder text
Firing Events
To fire events like change and click we import the fireEvent prop and then tag on the event name:
import { render, fireEvent } from "@testing-library/react"
it("fires change event", () => {
const { getByLabelText } = render(
<MyComp title="RTL" textBody="Testing 123455" />
)
fireEvent.change(getByLabelText("Type your email"), {
target: { value: "lol" },
})
expect(getByLabelText("Type your email").value).toBe("lol")
})
The second argument is usually setting up additional properties relating to the event. For the case of click:
// to fire simple left click
fireEvent.click(getByText("Submit"));
// to fire a right click
fireEvent.click(getByText("Submit"),{button:2})
})
Negative Assertions
Sometimes, its also handy to assert that a certain element is not in the DOM.
We can't use getBy
queries since they always expect the element to be found or they throw an error.
We use queryBy
instead since it will return null if nothing found like so:
expect(queryByText("an element which does not exist")).not.toBeInTheDocument()
Notice, we can preface any query with .not.
to indicate the inverse effect.
Targetting SVGs and Images
Images must always an alt
attribute associated with them and/or will have roles attached and so we can use getByRole
or getByAltText
to access them.
Mostly, SVGs will have one of the following 2 properties to be ARIA compliant:
(1) role=img
--> use getByRole
(2) title --> use getByTitle
Targetting by testid
In the rare case when you have no way of accessing the element, you can add a data-testid
attribute as a last resort to access it with getByTestId
query.
<p data-testid="unreachable"></p>
expect(getByTestId("unreachable")).toBeInTheDocument()
This is a last resort because it should never happen if you follow ARIA compliant rules. You should always have some meaningful ways of targeting your nodes/element as discussed above.