ReactJS Component Testing — Part 1 ( Basic Components )

Ananda Zukhruf Awalwi
7 min readJan 23, 2023

--

Software Engineer (Illustration)

In the software development lifecycle, application testing plays an important role in the cycle. In this case, application testing can be used as one of the parameters that the application is feasible to advance to the next phase.

In the past, the role of application testing was usually carried out by QA, but now, some IT Corporation says that application developers (*especially for middle+ level) are required to be able to have testing skills, like knowledge about using automation testing tools.

ReactJS + Jest

Usually for several programming languages, they have their own tools for doing automation testing. Call it JUnit for java, and of course there’s Jest and Enzyme for ReactJS. In this story, we will try to review ReactJS testing using JEST.

Lets get Started

Now open your terminal, and create new react project:

npx create-react-app react-jest-basic --template typescript

and also for Environment for testing react APP, you can copy my package.json :

// in package.json
{
"name": "react-jest-basic",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/node": "^16.18.11",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.7.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.4",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.2.1",
"@types/jest": "^29.0.0",
"babel-jest": "^29.0.3",
"babel-loader": "^8.2.5",
"jest-watch-typeahead": "^0.6.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Don’t forget to run “npm install” after that.

And Let’s create our basic login page like this :

//in Login.tsx

import React, { useState } from "react";
import "../styles/styles.css";
interface LoginProps {
username?: string | undefined;
password?: string | undefined;
onSubmit? : () => void;
}

const Login = (props : any) => {
const [disabled, setDisabled] = useState(true);
const [user, setUser] = useState<LoginProps>();
const handleChange = (e: any) => {
const name = e.target.name;
const value = e.target.value;
setUser((values) => ({ ...values, [name]: value }));

if (user) {
setDisabled(false);
} else {
setDisabled(true);
}
};
const handleSubmit = () => {
alert(user);
};
return (
<div className="login-container" role="login-component">
<form onSubmit={props.onSubmit ? props.onSubmit : handleSubmit}>
<label>Username</label>
<input name="username" value={props.username} onChange={handleChange} role="uname" />
<label>Password</label>
<input
name="password"
type="password"
onChange={handleChange}
value={props.password}
role="password"
/>
<button
role="btn"
className={`${disabled ? "disabled" : ""}`}
disabled={disabled}
type="submit"
>
Submit
</button>
</form>
</div>
);
};

export default Login;
/* in styles/styles.css */

.login-container{
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

form{
border: 1px solid black;
border-radius: 15px;
padding: 2rem;
display: flex;
flex-direction: column;
}

form input{
margin-bottom: 1rem;
}

.disabled{
}

and the result will be shown as below :

result of login page component

Create Unit Testing

after we create the page component, then we start to enter our main agenda, to make unit testing, first of all, let’s create Login.test.tsx :

Make Sure that component rendered

// inside Login.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import Login from './Login';

describe('Testing Login Component', () => {
beforeEach(() => {
render(<Login />);
})
it('should render Login page', () => {
const login = screen.getByRole('login-component');
expect(login).toBeInTheDocument();
});
})

In the code above, we can see the use of “role” which makes it easier for us to find the target component to be tested. After finished the code to test about rendering all of login page component, we can run “npm run test” to show us the result.

test result for ‘should render login page’

Test Username Field

// inside Login.test.tsx

import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Login from "./Login";

describe("Testing Login Component", () => {
beforeEach(() => {
render(<Login />);
});
it("should render Login page", () => {
const login = screen.getByRole("login-component");
expect(login).toBeInTheDocument();
});

// New Test Scenario (For Testing username field)
it("should render the value of username", () => {
// first identify the username field
const username = screen.getByRole<HTMLInputElement>("uname");
expect(username).toBeInTheDocument();

// adding userEvent to type in the field (it's likely a mock typing from user)
userEvent.type(username, "uwatere");
expect(username.value).toBe("uwatere");
});
});

In the code above, is an example of how to test the username field, if you look at the steps there are 2 steps, namely to identify the field and do mock typing where the test parameter is that the text input must match the output text.

Result of test case 2

if you are curious, you can try to change the output text so that it doesn’t match the input text like this:


// adding userEvent to type in the field (it's likely a mock typing from user)
userEvent.type(username, "uwatere");
expect(username.value).toBe("uwatobi");

then you will get an error or test fail when you do the testing :

Result of test case 2 (if error)

Test Password Field

import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Login from "./Login";

describe("Testing Login Component", () => {
beforeEach(() => {
render(<Login />);
});
it("should render Login page", () => {
...
});

it("should render the value of username", () => {
...
});


// New test scenario
it("password field should have password type", () => {
// first identifier the password field
const password = screen.getByRole<HTMLInputElement>("password");
expect(password).toHaveAttribute("type", "password");
});

it("should render the value of password", () => {
// first identifier the password field
const password = screen.getByRole<HTMLInputElement>("password");
expect(password).toBeInTheDocument();
// adding userEvent to type in the field (it's likely a mock typing from user)
userEvent.type(password, "123");
expect(password.value).toBe("123");
});
});

To test the password field, there will be 2 cases given. the first is to ensure that the password field has type=’password’, and the second case is that the output password must match the input. For testing input — output, the method is still the same as we did for the username field.

Here’s for the result :

Result of testing Password field

Test Button Component

import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Login from "./Login";

describe("Testing Login Component", () => {
beforeEach(() => {
render(<Login />);
});
it("should render Login page", () => {
...
});

it("should render the value of username", () => {
...
});

it("password field should have password type", () => {
...
});

it("should render the value of password", () => {
...
});

// New Test Scenario

it("should have class disabled", () => {
const btn = screen.getByRole("btn");
expect(btn.classList.contains("disabled")).toBeTruthy();
});

it("should calling handleSubmit when submit btn clicked", () => {
const handleSubmit = jest.fn();
const loginwithSubmit = (
<Login onSubmit={handleSubmit} username="uwatere" password="1234" />
);
render(loginwithSubmit);

// first identifier the button component
const btn = screen.getAllByRole("btn");

// trigger clicking button
fireEvent.submit(btn[1]);
expect(handleSubmit).toHaveBeenCalled();
});
});

As we have written in the code, that the submit button will have “class=disabled” if disabled is true, and disabled only changes to a false value if the user object already exists (in this case the user has filled in the username and password fields).

So for the component button there will be 2 test cases, namely to determine whether the button has class = ‘disabled’ at idle, and the second is to ensure that when the submit button is clicked, it will trigger the handleSubmit function.

And also, for functional testing cases like handleSubmit, we need to need a mock function for the handleSubmit itself, so we make handleSubmit = jest.fn() as the mock function.

Here’s for the result :

Result for all test case for the Login Page Component

Conclusion

After doing the example using the test case above, we can draw several important points:
1. Using the “role” attribute to facilitate component identification.
2. Using “userEvent” to simulate typing or input on the field.
3. Use “toHaveAttribute( )” to check certain attributes in the component.
4. Using “classlist” to see whether a particular class has been implemented or not.
5. Using “jest.fn( )” as a mock function for testing.
6. Use “fireEvent” to mock the action on the component.

Thanks for reading my story. sorry if there are still many shortcomings in terms of editorial writing and words used, all kinds of feedback will really help me in order to improve the quality of my writing in the future. Thanks

--

--

Ananda Zukhruf Awalwi

Front-end dev & stock market enthusiast. Sharing insights on tech & finance. Join me on my journey to learn & grow #frontenddevelopment #stockmarket