I’m Building An App – Step 4 – Unit Tests

In the previous post we built a database sanitization function but how do we know it works other than testing directly in the app and looking at the database? We can actually do this with Unit Tests.

In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use.

https://en.wikipedia.org/wiki/Unit_testing

The important bit about Unit Tests is that they should test functionality independent of external systems. This means that this app’s unit tests should only test the self-contained functions within it and not test any functions that interact with my database or with the Auth system(anything to do with Firebase, really).

Testing functionality which integrates with external systems does have its purpose, however, that would fall under integration testing.

Jest

I’m going to be using Jest for unit testing here. Jest is an open-source testing framework created by Facebook with the help of many other contributors. It’s widely used by a number of giant tech companies to quickly and easily set up JavaScript unit tests.

Since I will be performing unit tests while developing, I’ll want to make sure to only install it as a devDependency:

npm install --save-dev jest

In other words, we don’t want Jest to be a dependency in the production environment.

A Gotcha

It’s important to remember that you should be exporting the functions you are testing in your app as modules and then importing them into your tests. I have caught myself simply copying and pasting the functions from my production code into my tests, which is wrong. Not only is it not DRY but if I change those functions in my production code the tests will not reflect those changes.

In my /functions/src/index.js file I have a function which strips HTML from each of the values of the properties of an object. I export it to make it available outside of this file.

exports.stripHtmlFromObj = ( data ) => {
    for (const prop in data) {
        data[prop] = sanitizeData(data[prop])
    }
    return data
}

Importing this function into my test.js file is as simple as this:

const { stripHtmlFromObj } = require('../functions/src/index.js')

The reason we import it between a set of curly braces is because importing the file by itself imports one big object with all of the available functions and variables that were exported from that file. We are using object destructuring to only grab the stripHtmlFromObj() function.

An Example

We created a function that would sanitize input data before it is sent to the server. It works by stripping out HTML from raw text. The main inputs we have in our app are the inputs used when adding a new contact to the app. Sounds like a good thing to test.

First, let’s try to describe what a test would do in plain English.

  • Import our own sanitization function so we can use it in our test.
  • Create an example data object that would represent a contact.
  • Add some HTML to the data object string values.
  • Write tests to check if the HTML is stripped when passed through our own sanitization function.
  • We expect that when we pass a string with HTML through the sanitization function that we return a string minus the HTML.

If I pass the following for Last Name:

"lastName": "<script>alert('you are screwed');</script>Shellberg"

It should strip out HTML tags from the contact.

I expect the resulting lastName property to equal “Shellberg”.

The bold portions of that last sentence are not a random style choice; we effectively just wrote a Jest unit test expectation. 🙂

The Code

Here’s an excerpt of the contacts.test.js file which tests functions that manipulate/read contact data:

const { stripHtmlFromObj } = require('../functions/src/')

describe( 'sanitize HTML inputs', () => {

    const data = {
        "id": 1,
        "firstName": "<strong>Thomas</strong>",
        "lastName": "<script>alert('you are screwed');</script>Shellberg",
        "birthday": "Sat May 25 2019 12:34:33 GMT+0200 (Central European Summer Time)",
        "phone": "(480)555-5555",
        "email": "greg@gregerson.com",
        "address": "21 Main St., Phoenix, 85224, Arizona"
    }

    it('should strip out HTML tags from contact', () => {
        const strippedData = stripHtmlFromObj(data)
        expect(strippedData.firstName).toEqual('Thomas')
    })

    it('should strip out HTML script tags from contact', () => {
        const strippedData = stripHtmlFromObj(data)
        expect(strippedData.lastName).toEqual('Shellberg')
    })

    it('should leave birthday string intact', () => {
        const strippedData = stripHtmlFromObj(data)
        expect(strippedData.birthday).toEqual("Sat May 25 2019 12:34:33 GMT+0200 (Central European Summer Time)")
    })

    it( 'should leave phone number intact', () => {
        const strippedData = stripHtmlFromObj(data)
        expect(strippedData.phone).toEqual("(480)555-5555")
    } )
})

Test Result

All tests passed!

Parting Thoughts

We want to remember that unit tests should test functionality independent of external systems. This means that this app’s unit tests should only test the self-contained functions within it and not test any functions that interact with Firebase.

When writing a unit test, a best practice is to write a test for every possible code route. Let’s say you have a function with an if{} else{} statement which diverts the function into two directions of flow. You should write a test for each of those routes.

In this case we don’t have that sort of code diversion but we do have multiple ways that data could be entered. Since we know that we want to test HTML being sanitized from our data string, it’s easy to only write tests in which all of the data is dirty. We have to remember to also test clean(no HTML) data.

Therefore, since our data object contains 6 properties, we should probably write at least 12 unit tests here(6 properties that are dirty, 6 that are already clean).

I’ll be refactoring this code to actually put it into two different suites; one for “dirty” inputs and another for “clean” inputs.

Unit testing React Native apps is not easy as the separation between pure functions and interactivity can be a gray area, however, we should do our best to unit test our pure functions with Jest and use additional testing libraries for integration testing or end-to-end testing.

Next

The next post of this series will be one of the easiest; we’ll be adding Typescript to the project and slowly converting some of our code to Typescript.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: