Testing your Front-end Application in a Right Way

Image for post
Image for post

Developing an application without bugs is almost impossible. Especially when we need to develop major features and each of them needs to be implemented by a different developer. Once these features will be integrated, bugs likely will show themself. Obviously, we never want to bugs exist on our codes intentionally, it just appears accidentally due ignorance of other edge cases or interprets a feature incorrectly. For the sake of business, we don’t want our users or customers to taste those bugs, that’s why we need to test our codes before it launches to production.

Testing is a crucial task, besides it ensures the quality of your codes, it helps you to keep focus on the feature you are currently working on without having worry about existing features. In practice, actually we create a lot of small independent tasks for the test, each of them we called it a unit test. The unit test is a black-box way to test, there is no need to know what happens inside, just concern about what we expect after we give something to the function we want to test. For back-end, creating a test is clearly implementable since all existing functions give you clear input parameters and output. Front-end engineers found it hard to test like what unit-test does because we want to test the shape or content of the component that has a lot of ambiguity in terms of style and layout implementation. In other words, the output is not much clear for a test task to test, that’s why some developers achieved testing by creating a test that needs to look inside rather than treat your test to see as normal people do to your component.

In any worlds, test in a white-box way is never a good choice. It’s not independent for developers, they need to rely so much on the test we created. In this article, I want to share with you how I and my team test our components in front-end in a black-box way as possible. This article will cover basic knowledge of testing in front-end and I will do it on React Native. But don’t worry, surely it’s also applicable to other front-end frameworks.

Setup test

We are using Jest to test our React application both on the web and mobile. In simple words for the explanation, jest is a test framework that gives us the ability to detect test files, select codes that need to be considered as testable codes, show coverage codes, and others. Check this out for further exploration. It officially comes from React itself after we initialize the application for the first time. So there is no need to do for installing a Jest at first. Let’s go to the real things now.

Since all our source codes are inside the src directory and using Typescript, then this is how we tell jest to locate and recognize those on mobile.

On /package.json we put these lines

"jest": {
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
]
},

Note that there is .tsx extension there to indicate React Typescript file. We don’t need to tell Jest where exactly our test file is, as I mentioned before Jest would automatically find a test file by itself, Jest recognizes a test file by looking at the file name that follows this pattern.

*.test.*\

As we know a good unit test should be an atomic representation of functionality. For doing so, we mark a component as that atomic representation. We decided to put a component code and a test file in the same directory, then the test file only test all codes within the same directory. This how it looks like:

Inside components, we defined each test (index.test.tsx) in the same level hierarchy of component (index.tsx)

In a management view, it is more maintainable and accessible to work with such management rather than define all tests in one directory or one file somewhere in a project. When we had already large source codes, it’s roughly hard to find your test file in no time. Additionally, if you come with a similar name between files it tends to deceive your eyes to find a test in the same directory or file, even you are able to use search tools on your IDE then you need to memorize file name before you search it, it’s still not a good practice though. At another time, I would tell you more about this management. Let’s get back to the topic again.

Let’s say we want to test this component, a Button.

on /src/components/Button/index.tsx

import React, { useContext } from 'react';
import styled, { ThemeContext } from 'styled-components/native';
import {
Text
} from 'components';
interface ParentProps {
width?: string;
}
interface StyledButtonProps extends ParentProps {
background: string;
borderWidth?: string;
borderColor?: string;
theme: ThemeProps;
}
const StyledButton = styled.TouchableOpacity`
justify-content: center;
align-items: center;
padding: 12px 36px;
height: auto;
width: auto;
flex: 1 0 auto;
width: ${(props: StyledButtonProps) => props.width || "auto"};
background: ${(props: StyledButtonProps) => props.background};
border-width: ${(props: StyledButtonProps) => props.borderWidth || '0px'};
border-color: ${(props: StyledButtonProps) => props.borderColor || 'transparent'};
border-radius: 3px;
`;
enum ButtonType {
Filled = 1,
Outline = 2,
}
interface ButtonProps extends ParentProps {
type?: ButtonType,
children: string;
clickable?: boolean;
scale?: number;
isBold?: boolean;
onPress?: () => void;
}
const DEFAULT_MAIN_COLOR = "#42c41d";
const DEFAULT_DISABLE_COLOR = "#646663";
const DEFAULT_TEXT_COLOR = "#9d9e9d";
function Button({
type = ButtonType.Filled,
children,
clickable = true,
isBold = true,
width,
onPress = () => {}
}: ButtonProps) {
const { colors } = useContext(ThemeContext) || {};
// Define all colors
const filledButtonMainColor: string = colors?.green || DEFAULT_MAIN_COLOR;
const filledButtonDisableColor: string = colors?.lightGray || DEFAULT_DISABLE_COLOR;
const filledButtonTextColor: string = colors?.almostWhite || DEFAULT_TEXT_COLOR;
const outlineButtonMainColor: string = colors?.black || DEFAULT_MAIN_COLOR;
const outlineButtonDisableColor: string = colors?.mediumGray || DEFAULT_DISABLE_COLOR;
const outlineButtonTextColor: string = colors?.black || DEFAULT_TEXT_COLOR;
if (type === ButtonType.Filled) {
return (
<StyledButton
onPress={clickable? onPress: () => {}}
width={width}
background={clickable? filledButtonMainColor: filledButtonDisableColor}
>
<Text
type={Text.StyleType.Medium}
color={filledButtonTextColor}
isBold={isBold}
>
{children}
</Text>
</StyledButton>
);
} else {
return (
<StyledButton
onPress={clickable? onPress: () => {}}
width={width}
background="transparent"
borderWidth='0.8px'
borderColor={clickable? outlineButtonMainColor: outlineButtonDisableColor}
>
<Text
type={Text.StyleType.Medium}
color={clickable? outlineButtonTextColor: outlineButtonDisableColor}
isBold={isBold}
>
{children}
</Text>
</StyledButton>
)
}
}
Button.Type = ButtonType;export default Button;

Just focus on the Button function. In summary, there are types of buttons, parameters to customize the Button component, and also component itself that is able to be clicked whenever a clickable parameter is true.

Assume we are outsiders that don’t know a specific location of component on Button that able to click, but we do know what type of clickable component. In ReactNative, a clickable component is TouchableOpacity. So the way we test our component is first finding the location of TouchableOpacity inside the Button component, after we found it we can click this button as people do by call onPress function on their property.

The challenge is how we find TouchableOpacity inside the Button component. Luckily there is a library that doing so for us, introduce you, react-test-renderer. This library does pretty much for us, such as test whether the component able to render properly or not, and also find something we want inside the component without knowing full information of the component.

This is full codes of test for Button component

on /src/components/Button/index.test.tsx

/**
* @format
*/
import React from 'react';
import { TouchableOpacity } from 'react-native';
import Button from '.';
import renderer from 'react-test-renderer';
// Note: test renderer must be required after react-native.
describe("Button tests", () => {
describe("Filled button test", () => {
it('Filled button works properly', () => {
// Default button with a proper callback function
let number = 0;
const button = renderer.create(
<Button
onPress={() => number += 1}
>
Button
</Button>
);
expect(button).toBeTruthy();
button.root.findByType(TouchableOpacity).props.onPress();
expect(number).toEqual(1);
});
it('Prevent action for unclickable filled button', () => {
let number = 0;
const button = renderer.create(
<Button
clickable={false}
onPress={() => number += 1}
>
Button
</Button>
);

expect(button).toBeTruthy();
button.root.findByType(TouchableOpacity).props.onPress();
expect(number).toEqual(0);
})
})
describe("Outline button test", () => {
it('Outline button works properly', () => {
let number = 0;
const button = renderer.create(
<Button
type={Button.Type.Outline}
onPress={() => number += 1}
>
Button
</Button>
);
expect(button).toBeTruthy();
button.root.findByType(TouchableOpacity).props.onPress();
expect(number).toEqual(1);
});
it('Prevent action for unclickable outline button', () => {
let number = 0;
const button = renderer.create(
<Button
type={Button.Type.Outline}
clickable={false}
onPress={() => number += 1}
>
Button
</Button>
);

expect(button).toBeTruthy();
button.root.findByType(TouchableOpacity).props.onPress();
expect(number).toEqual(0);
})
})
})

As you can see there is no need to look states or variable inside components, we only look component as people do. This test we called it a functional test, this what we did it in best practice.

These simple test codes follow all of TDD FIRST principles as well. Notice we do a lot of different tests with different parameters in the same component because we want to cover all possible customizing and clearly the test is independent, so it’s able to run repeatedly without worry to break another system or functionality with the same result as long there is no changes significantly inside components. Also, the number of codes is not too much rather than the codes of the component itself so we are able to write it in no time. Since it’s an automatic test then it’s self-checking. The test is also so fast to run, as you can see a screenshot that I attached below

Image for post
Image for post
In total 58 ms need to run

Takeaways

The thing that you always need to remember is always trying to achieve a black-box way to test as possible, it’s an absolute choice. This article is just covering one of the scenarios to test, in the real-word, there are a lot of codes hardly tested by this method. Let’s say test a form, in practice, it’s easier to test a state than test a couple of fields and buttons, but still could be solved this way. The point is don’t give up, keep practice, by doing this way actually we help yourself and other developers in order to refactor or implement component independently without knowing how a test task tests our codes. Note, this concept is not only for React Native but also applicable to other front-end frameworks.

Written by

Mainly a Full-stack Developer, Software Architecture Engineer. I'm a passionate developer, love to learn new things and write an article about it

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store