Why write automated tests?
Automated tests give you confidence in the piece of code you write. Sometimes we write code that works locally then push to production. However, changes made breaks entirely different parts of the project.
Someone else on your team might have written these parts of the project, but your changes caused conflicts. Testing prevents this.
There are different types of tests: unit tests, integration tests, functional tests, end-to-end tests, and many more.
What is unit testing?
Using a car as an analogy, we all know that a car comprises of different components (Unless you live in a tree 🤪). These components comprise wheels, an engine, headlights, and taillights, etc.
Unit tests basically ensure each individual component performs as expected. Looking at our car analogy, to ensure our wheel is perfectly round we rotate it by exerting external force, the same way we write unit tests to ensure individual components of our source code (software) function as they ought to.
In this article, we will be looking at the basic principles of unit tests with a simple react-native Button component.
Let’s go-ahead to create a Button component.
We test react-native using several tools. The first tool we will look at is Jest.
React Native ships with Jest; we can skip its installation.
‘Jest’ discovers test files within our project via their file names, which can be at any depth in our project. There are three naming styles by which ‘Jest’ picks up our test files.
- Any file with a .test.js suffix or a .spec.js suffix — This is suitable for finding associated test files for a component. When you have App.js the test file is side-by-side as App.test.js or App.spec.js
- Any files witing a test folder — This is preferrable for components that require multiple test files and large projects.
Enzyme — Enzyme helps test the output (rendering), interactivity, and manipulation of our React and React native components by simulation, as compiling and running our react-native application for testing would be a slow process. Enzyme’s syntax makes our lives easier while testing components 😁. An alternative to Enzyme is ‘react-test-renderer’, but we will make use of Enzyme as it contains more functionalities.
To setup enzyme first run -
npm install jest-environment-enzyme jest-enzyme enzyme-adapter-react-16 — save-dev
npm install — save-dev react-dom enzyme
Edit package.json — Add the following config to “jest” object
So let's get to it!
Testing our Button component
describe, it, expect- Are global variables in ‘Jest’; as a result, we don’t have to require or import anything to use them.
describe(name, cb)- This method creates a block or scope where we write several related tests. 'describe' takes in a name (should be descriptive) and then a callback where we write our actual tests
it(name, cb, timeout)- ‘it’ is an alias of test which has three parameters — a descriptive name of your choice, a callback function containing our test assertions, and an optional timeout that specifies how long to wait before the test aborts. The default timeout is 5 seconds.
expect(value) - Expect is used when we want to test a value, we mostly use 'expect' alongside other functions called "matchers", to view all matchers you can check the link Expect · Jest
Testing “Button”, we call the describe method passing in a description and a callback indicating a shared relationship of every test assertions in the describe block. Then our first test assertion — it(‘should render without issues’, ….) makes sure the Button renders.
Using shallow from Enzyme, we run the Button component in isolation without rendering its children (In this instance, Button has no child component), shallow calls the constructor of the component and the component’s render method.
The number of components rendered (component.length) is expected to equal one using expect and the toBe() matcher. toBe() compares values or object instances, making it more suitable to the === operator.
Snapshots —We use snapshots to make sure we don’t cause unexpected changes to UI components. When we make deliberate changes to components, the snapshots test fails; as a result, we update our previous snapshots using npm test — -u
We use the .toMatchSnapshot() to compare previous and current snapshots. When there isn’t an old snapshot, ‘Jest’ creates a new one.
To compare snapshots we use the toJson(component) method; it returns a compatible format of our component for ‘Jest’ snapshots.
The next thing we want to test is the onPress event prop, but before doing, that let’s look at Mock functions in Jest.
Mock functions — In Jest, mock functions allow us to test links between code. Jest mock functions let us erase the actual implementation, more like a placeholder. For example, testing a component that makes actual API calls, our test will be slow; hence we mock the API endpoint.
There are two ways to mock functions in ‘Jest’:
- Creating mock functions in test scripts.
- Writing manual mocks to override dependencies.
For now, we will mock up a simple function below.
We track of mock functions calls and return values; using the .mock property. This is done using the .calls, .return, .instances properties.
We create a mock function which returns ‘I was Pressed’, then render our component using shallow(). Afterwards simulate a press by calling .props().onPress(). The method .props returns an object containing all props of a component. Considering the normal behavior of a button, pressing the Button component only once should call our mock function — which returns ‘I was Pressed’. To test for this, expect() is called twice. Ensuring mockFn is called once, and its return value is correct.
The next thing we want to make sure of is onPress not calling mockFn when isLoading is true.
When isLoading prop is true, we simulate a press on Button, which shouldn’t trigger a call to mockFn. Therefore the number of calls to mockFn remains 0.
Next, we test if Button renders the correct title.
We use the .find method to obtain the Text component in Button, the child of Text should be the same as the title prop.
Next, we test if the title of Button renders ‘Loading…’ when isLoading === true
We use setProps to update the props passed to Button and force a re-render, then we check if the title rendered is “Loading…”
Next, we test if the styles of Button change when we pass the transparent prop to Button.
We use the .toMatchObject to check if our style objects are correct.
Finally, we test by running npm run test
Our test cases pass, yeee! 😝
Testing is like our best friend, saving us from a lot of troubles. Testing our code prevents us from pushing breaking changes to production, forces us to write quality code, serves as a form of documentation, find bugs early, and a lot of other advantages.
For further readings and API references, check out the links below: