An Overview of Writing Testable JavaScript

Alt Text

Over the time period, we have better options for unit testing JavaScript. However, it does not mean that the code which is tested is as easy on us as our tools are! Writing and managing the codes takes some effort and planning, but functional programming concepts inspire few patterns which can be used to avoid getting into a tight spot while testing our code.

Here in this article, we will go through some useful tips and patterns for writing testable code in JavaScript.

Maintaining distance between Business Logic and Display Logic

A JavaScript-based browser application basically listens to DOM events which are activated by the end user and then responding to them by applying some business logic and displaying the results on the page. But the problem here is how to simulate DOM events to test your anonymous function.

So instead of writing a named function, pass it to the event handler. It applies to more than the DOM, though. A plenty of APIs which are discovered in the browser and the Node are designed to events or wait for other types of asynchronous work to complete.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// hard to test
$('.show').on('click', () => {
    $.getJSON('/file_path')
        .then(data => {
            $('#output-list').html('output: ' + data.join(', '));
        });
});

// testable; we can directly run fetchList to see if it
// makes an AJAX request without triggering DOM
// events, and we can run showList directly to see that it
// displays data in the DOM without AJAX request
$('.show').on('click', () => fetchList(showList));

function fetchList(callback) {
    $.getJSON('/file_path').then(callback);
}

function showList(data) {
    $('#output-list').html('output: ' + data.join(', '));
}

Using Callbacks or Promises with Asynchronous Code

In the above illustration, our refactored fetchResult function runs an AJAX request. It symbolizes that we cannot function and test as everything we expected.

Apart from this, one of the most popular ways to organise asynchronous code is to use Promise API. Fortunately, $.ajax and most other of jQuery’s asynchronous functions has a likely return, so a lot of common use cases are already covered.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// hard to test; we don't know for how long the AJAX request will stay
function fetchResult() {
    $.ajax({ url: '/file_path' });
}

// testable; by passing a callback and run assertions
function fetchResultWithCallback(callback) {
    $.ajax({
        url: '/file_path',
        success: callback,
    });
}

// also testable; run assertions after the returned Promise resolves
function fetchResultWithPromise() {
    return $.ajax({ url: '/file_path' });
}

Avoid Side Effects

Now it is the time to take arguments and return a value based solely on those arguments. If your function depends on some external state, you have to perform a setup in your tests. You’ll have to trust that any other code being run isn’t modifying that same state. In the same mood, avoid writing functions that alter external state. This prevents the adverse effect on your ability to test other code.

1
2
3
4
5
6
7
8
9
10
11
12
13
// hard to test; we have to set up a globalListOfBikes object and set up a
// DOM with a #model-list node to test this code
function processBikeData() {
    const models = globalListOfBikes.map(bike => bike.model);
    $('#model-list').html(models.join(', '));
}

// easy to test; pass an argument and proceed to test its return value, without
// set any values on the window or check the DOM the result
function buildModelsString(bikes) {
    const models = bikes.map(bike => bike.model);
    return models.join(',');
}

Don’t change Parameters

Create a new object or array in code and then proceed to add values to it. Or, use Underscore or Lodash to clone the passed object or array before using on it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/ alters objects passed to it
function upperCaseLocation(clientInfo) {
    clientInfo.location = clientInfo.location.toUpperCase();
    return clientInfo;
}

// sends a new object back instead
function upperCaseLocation(clientInfo) {
    return {
        name: clientInfo.name,
        location: clientInfo.location.toUpperCase(),
        age: clientInfo.age
    };
}

Writing Test before Coding

A test driven development (TDD) is the process of writing unit tests before the code. A lot of developers find TDD to be very helpful. In practice, TDD is a method that can be difficult to commit to all your code changes. But when it seems worth trying, it’s an excellent way to guarantee you are keeping all code testable.

I hope these tips will help you remember, to keep your code simple and functional as much as possible, this will keep your test coverage high and overall code complexity low!

1174
  • Sophia Phillips

  • Article written by Sophia Phillips. Sophia Phillips is expert frond-end & wordpress developer. Currently, she is an employed with WordPrax Ltd. a renowned name in PSD to WordPress conversion services. Sophia has had written a remarkable number of articles on WordPress tricks and tips.

  • Follow Sophia Phillips @ www.wordprax.com

3 Comments Add Comment