Unit Testing on JS: Functional vs Imperative
Posted 03 June, 2020
#Technology

Unit Testing on JS: Functional vs Imperative

Nowadays, JavaScript is all over the place. As the code evolved, testing libraries and strategies like TDD and BDD also did. Indeed, we all agree on the same thing: Code must be tested.

In the past years, a big part of the community shifted towards Functional programming. As a side effect, the paradigm shift brought us a more straightforward way of defining and writing unit tests.

So the thing is …

How?

What is Functional programming?

Functional programming is a programming paradigm — a style of building the structure and elements of computer programs — that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data — wikipedia

Let's take a look at an example of code which will be the system under test:

const showResponseMessage = (response, source) => {
  let message = source.message;
  if(response.data.status) {
    message += response.data.status;
  }
  if(response.data.title) {
    message += " " + response.data.title;
  }
  return message;
}

Pretty simple, right? The function receives a response and a source, we compose the message based on data received on the response, and we return it. Let's test it!

describe('It should show the error message', ()=> {
  const response = {data: {status: 'fail', title: 'edit'}}
  const source = { message: 'The post action had '}
  it('Adds the extra info', ()=> {
    expect(showResponseMessage(response, status)).toEqual('The post action had fail edit')   
  })
})

Looks simple, right? Straight forward I'd say, let's improve it:

const showResponseMessage = (response, source) => {
  let messageResponse = getMessage(source);
  const {status, title} = response.data;
  messageResponse = addStatus(status, messageResponse);
  messageResponse = addTitle(status, messageResponse);
  return messageResponse;
}

const getMessage = source => source.message;

const addStatus = (status, message) => {
  return message + " " + status
}

const addTitle = (title, message) => {
  return message + " " + title 
}

I know, the number of code lines increased, at a glance, it just seems like a more sophisticated way of achieving the same thing. But, we increased the number of tests too, enhancing the overall quality assurance.

describe('It should show the error message', ()=> {
  const source = { message: 'The post action had'}
  const response = {data: {status: 'fail', title: 'edit'}}
  const {status, title} = response.data;

  it('gets the message from source', ()=> {
    expect(getMessage(source)).toEqual('The post action had')   
  })

  it('Adds Title properly', ()=> {
    let message = getMessage(source);
    expect(addTitle(title, message)).toEqual('The post action had  edit')
  })

  it('Adds Status properly', ()=> {
    let message = getMessage(source);
    expect(addStatus(status, message)).toEqual('The post action had  fail')
  })

  it('Adds returns the right message', ()=> {
    expect(showResponseMessage(response, source)).toEqual('The post   action had fail edit')
  })
})

In the first example, if we change the structure of the response and title, the test will fail, but we won't know why the test failed. Did it fail because of a missing response message or a wrong title retrieved as part of the response?

In the second example, we are testing each functionality separately. By assigning each function it's own responsibility, we make the code more maintainable, efficient, and versatile. For more information, check the single responsibility principle.

Let's consider the following scenario, suppose that our business logic changes, and we want to prevent the spaces on title and status. In such a case, we will have to create a function that receives a text and returns the sanitized version. To test that function, we have to create a test that solely asses that requirement; this test will not be related to showResponseMessage, but to space trimming.

That example points out a single case. However, as the logic and size of the application grow, the more relevant this approach to testing will become. When we start working with async calls, with the ability of composition and Higher-Order functions, we can make our "features" readable and our functions real units.

Furthermore, all of our original values are immutable. In the first example, we modified the message variable, now we don't, we still have our original message — And why is that important? Mutation means change, change adds complexity, opens the door for bugs, and sometimes makes things unpredictable.

When our code is predictable, self-explanatory, and easier to track and read, it's much easier to write cleaner tests.

"Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write a new code. ...[Therefore,] making it easy to read makes it easier to write." Robert C Martin.

So stay functional for simple testing and happier code reviews :)

Photo by Markus Spiske on Unsplash

Avatar Img
Damian Cardozo

Newest Posts

Meet our office in Salt Lake City
Posted 27 October, 2022

Meet our office in Salt Lake City

We've been working in offices for a long time. And we've learned that there's no one right way to do it. That's why when we opened our new commercial office in Salt Lake City, we did it differently the traditional way. We work in an open space, yes—but we also have private offices if we need one. Our office is part of a global network of workspaces that gives all the benefits of an open office without sacrificing privacy or productivity. During our stay in SLC from September 24th to October

#Announcements Business
Silicon Slopes Summit: a World-Class Business & Tech Event
Posted 20 October, 2022

Silicon Slopes Summit: a World-Class Business & Tech Event

Silicon Slopes Summit is one of the largest and most recognized business and technology events in the world. As every year, the sixth edition took place in Salt Lake City, Utah, being celebrated for the first time at the Vivint Arena.

#Events

Book Us

You call it a challenge? We are ready, bring it on