Please add sugar first (and some testable code)

Why do I have to write all these tests? It’s more code! What’s the point?

The amount of code in a comprehensive unit test might equate to the amount in the production module that’s being tested. So why double the code?

Think about that application you just built. It contains a myriad of features, and a few are particularly complex. How long would it take you to parse through the code base, find that one spot you need to test, and then proceed to implement it? Think about it. Your day would go far smoother simply writing out some extra lines of code that target your test specifications.

But wait. There’s more.

Sure, you can start a project by writing the functional code first, and slap on those tests at the very end. However, doing so risks creating code that is harder to test due to structural problems such as dependencies and modules organized in ways that would prove difficult when those tests start to run. Think of testing as the sugar you need to add when making some delicious brownies. If you never add in the sweet stuff, no amount of toppings is going to make for that tasty core. It works the same way with those assertions. The entire blueprint of the code changes when you start the development process with a test driven mindset.

At this point you might ask: What exactly is code that is “hard to test”?

It’s actually quite easy to spot. Let’s say you are writing a unit test for a particular module. The difficulty of writing that unit test will depend on the structure of the code. Does the module contain a giant function with multiple degrees of functionality? Are there heavily nested logical operations that utilize different private functions and variables? At some point, you begin to lose control of the code.

Yes, you can still manage to write that unit test. But how clean will it be? How clear and concise will your assertions look if the subject of its operations is a kerfuffle’d code base? A test that is hard to read is essentially useless.

This is why it is important to have tests baked into your code, and not added in as an act of desperation. It is important to understand that the very act of writing tests while developing changes the mindset of how you present the code.

You may be a fresh-off-the-boat developer, or a veteran with decades of experience, and writing tests can seem daunting at first. But never fear. In the end, testing is a skill like any other skill. It’s going to take a lot of practice, but it will lead to better code, cleaner tests, and a bunch of happier developers!

 

Advertisements

A Promise to Escape the realm of Callback Hell

Let’s begin with a story.

You are creating an image gallery dedicated to steak lovers around the world. As a way of adding a little spice to the presentation, you decide to allow five steaks to fade onto the screen, one after another. In order to achieve this, you’ll want to make sure that the images are successfully loaded before you attempt to animate each one.

No problem right? You stretch out your hands, do a couple fist pumps, and proceed to unleash the fury and power of the Javascript callback unto the unsuspecting editor.

function imageCallBack(url, callback) {
  let img = new Image();
  
  img.onload = function() {
    callback(null, img);
  }

  img.onerror = function() {
    callback(new Error('Image was not loaded'));
  }

  img.src = url;
}

Let’s see how this will look when you try to chain a series of nested callback functions. We assume that a function named “animateImage()” will be called after the image has been successfully loaded.

imageCallBack('img/steak1.png', (err, steak1) => {
  if err throw err;
  animateImage(steak1, 'fadein');
  imageCallBack('img/steak2.png', (err, steak2) => {
    if err throw err;
    animateImage(steak2, 'fadein');
    imageCallBack('img/steak3.png', (err, steak3) => {
      if err throw err;
      animateImage(steak3, 'fadein');
      imageCallBack('img/steak4.png, (err, steak4) => {
        if err throw err;
        animateImage(steak4, 'fadein');
      });
    });
  });
});

Something seems wrong here. The code is starting to look real ugly.

Imagine if you had to debug five, six, seven, or even ten nested callbacks, each with its own layer of complexity. That would be absolutely terrifying.

Welcome to “callback hell”.

Looking at the code above, there are tons of repetition in error handling and way too many brackets. Is there a better way to handle such a request?

Thankfully, ES6 has given us the power of Promises, which allow us to rid the callback nesting that can occur when multiple asynchronous requests need to be made that also happen to depend on the outcome of each other.

Here’s the original callback function refactored and utilizing Promises.

function imagePromise(url) {
  return new Promise((resolve, reject) => {
    let img = new Image();

    img.onload = function() {
      resolve(img);
    }

    img.onerror = function() {
      reject(new Error('Image was not loaded'));
    }

    img.src = url;
  });
}

We see that the function is now returning a new Promise object. Its constructor has two arguments: resolve and reject. As you might have guessed, a resolved Promise returns the unravelled value while a rejected Promise will return an error.

Once you have a reference to a Promise object, you can call the “.then()” method to carry out an action if the Promise has been resolved.

The resulting code is much easier to read, understand, and debug.

Promise.all([
  imagePromise('steak1'),
  imagePromise('steak2'),
  imagePromise('steak3'),
  imagePromise('steak4'),
  imagePromise('steak5')
])
  .then(steaks => {
    steaks.forEach(steak => animateImage(steak, 'fadein'));
})
  .catch(err => {
    console.error(err);
});

This is the power of Promises in Javascript. They accomplish the same things as normal callbacks, but possess a nicer syntax and the ability to be chained in various ways.

Does this mean that all callbacks should be replaced with Promises? Probably not.

There are times when utilizing a callback is necessary because the callback needs to be run synchronously and more than once. Think of Javascript’s “forEach()” Array method and how it might be built by utilizing a callback for every element in the array. In that particular scenario, a Promise would not be able to achieve what the callback is capable of doing.

As a general rule of thumb, it is best if you use the right tool for the job. Promises shine when you need to make asynchronous requests that depend on multiple other asynchronous requests. If you ever find yourself chaining endless callbacks back to back, you may need to give your eyes a little rest and break out the Promises!