DEV Community

Cover image for How frontend unit testing turned from a fling to a real thing for me
Mirjam Aulbach
Mirjam Aulbach

Posted on

How frontend unit testing turned from a fling to a real thing for me

This article is originally from Sep 21, 2017. I'm moving my content from medium to dev.to

It’s hard to get around the word "testing" if you’re on your way to become a professional programmer. Of course I read about it and even spend a few hours writing tests and solving artificial problems with test driven development. But I didn’t have the opportunity to apply my acquired knowledge in my day to day coding life. It just isn’t a thing at my job. We don’t write tests (yet, I want to change that) and so for a long time, all I knew about testing was more on an academic level.

How the fling started

Pet projects to the rescue!

I’m lucky to work on a few pet projects with experienced programmers. The most important thing I learn from my these projects are new or different workflows, best practices and patterns. In a project I’m currently working on I finally wrote real tests in a real environment. Hell, I had a lot of fun!

By all means, I’m still a bloody beginner when it comes to unit testing. But now I finally saw and felt all the benefits I only read about before. And yeah, I fell in love a bit!

Since a few days I’m working on a text about testing in the frontend with an example from said pet project. It helps me learn more about testing. At the same time I try to build a simple workflow to test my code for an non-testing environment, so I’ll be able to deliver more stable solutions. My new found feelings for testing are there, but I’m not really sure if and how they will work in my job.

Real world catching up — TIFU

Today, I received a bug ticket. For a simple Javascript feature I wrote a few months ago. I saw the description of the ticket, took one look into the code and I was furious at myself. I could see my error right away and it was a stupid one.

Background: The script should take an integer and give back a number formatted in the € currency format — a dot as thousands separator if needed and “Euro” at the end.

That’s what my script looked like:

function formatSum(sum) {
  var sumNum = sum.toString().split('');
  var sumFormatted = [];

  for (var i = 0; i < sumNum.length; i++) {
    var currNum = sumNum[i];

    if (i != 0 && i % 3 == 0) {
      sumFormatted.push('.');
    }

    sumFormatted.push(currNum);
  }

  if (sumFormatted.length > 0) {
    sumFormatted.push(' Euro');
  }

  return sumFormatted.join('');
}
Enter fullscreen mode Exit fullscreen mode

I wrote this almost 4 months ago and really, I’m not proud. Nobody noticed the error for quite a time, because the function was used to format integers delivered by an API and — coincidentally — they all had six digits for a while.

  • My first thought: Damn, you’re stupid.
  • My second thought: Damn, you’re so f*** stupid, you only checked your script on the live site. You didn’t test your damn code properly!
  • My third thought: Huh. You’re smarter now than back then.

Me and testing: Getting serious

Here’s what I did to fix the bug: I wrote tests. I didn’t touch the script, I didn’t changed a single thing. I opened my editor and I. Wrote. The. F. Tests.*

function assertEqual(actual, expected) {
  if (expected === actual) {
    console.info('[SUCCESS] Is ' + expected);
  } else {
    console.error('[ERROR] Expected ' + actual + ' to be ' + expected);
  }
}

function tests() {
  console.log('Test results');

  assertEqual(formatSum(1), '1 Euro');
  assertEqual(formatSum(12), '12 Euro');
  assertEqual(formatSum(123), '123 Euro');
  assertEqual(formatSum(1234), '1.234 Euro');
  assertEqual(formatSum(12345), '12.345 Euro');
  assertEqual(formatSum(123456), '123.456 Euro');
  assertEqual(formatSum(1234567), '1.234.567 Euro');
}

Enter fullscreen mode Exit fullscreen mode

I ran the tests.

I used an old grunt setup I did a while ago to practice TDD.

The bug ticket reported a false format with a 5 digit number and the test reproduced this — 123.45 Euro instead of 12.345 Euro.

I corrected my script.

function formatSum(sum) {
  var sumNum = sum.toString().split('').reverse();
  var sumFormatted = [];

 for (var i = 0; i < sumNum.length; i++) {
    var currNum = sumNum[i];

if (i != 0 && i % 3 == 0) {
        sumFormatted.push('.');
    }

sumFormatted.push(currNum);
  }

if (sumFormatted.length > 0) {
    sumFormatted.reverse();
    sumFormatted.push(' Euro');
  }

return sumFormatted.join('');
}
Enter fullscreen mode Exit fullscreen mode

Note: I didn’t touch it otherwise. I didn’t refactor it, I didn’t make it better, I didn’t make it prettier — I just fixed my error.

I ran my test again.

Now that's a view I like!

All done. I could push that change. It cost me about 10 minutes to curse me for a bit, write my tests and correct my script. That’s it! Now, there are tests for my function. I could refactor ist, write a new one — anything. While testing it.

What I learned today

  • I really like testing. We are, like, serious now!
  • There are so damn many things I have to learn about testing and I’m really looking forward to it.
  • I don’t have to wait for anyone or anything to write tests. Yeah, it would be best to work with a 100% code coverage and test allllll the things — but even one little test can improve my code and make me a better programer in the long run. And hey, I’m a perfectionist, I strive for improvement ;)
  • Testing doesn’t have to be time consuming and therefor expensive! On the contrary: If I had written proper tests in the first place, the second bug ticket would never have happened.
  • I write better code today than three months ago.
  • Committing (sometimes even writing) code without tests feels a bit like driving without seatbelt now.

Cover Image: Photo by Cerys Lowe on Unsplash

Top comments (3)

Collapse
 
tobiobeck profile image
Tobi Obeck

Hey Miri,
your post is a perfect example why it is important to write tests. Thanks for sharing!
I really liked your dead simple test setup.
At my work place we usually don't write tests, but I sometimes copy a function into jsfiddle to quickly throw in a bunch of inputs to check the expected outcome.

I did so with your problem and had a shot at implementing the algorithm myself.
I noticed that there are further cases that you might not have thought of.
Negative numbers and numbers with a decimal portion result in errors in your implementation.

here is my solution:

function formatNumberAsCurrencyEuro_Tobi(value) {
  if (typeof value === 'string') {
    return value;
  }

  let formattedValue = '';

  // these should be function parameters
  const thousandsSeparator = '.';
  const decimalSeparator = ',';
  const currencyName = 'Euro';

  let valueString = String(_.round(value, 2)); // lodash.round
  let decimalString = '';

  if (valueString.indexOf('.') !== -1) { // has decimal portion
    const splittedValue = valueString.split('.');
    valueString = splittedValue[0];
    decimalString = decimalSeparator + splittedValue[1];
  }

  for (var i = 0; i < valueString.length; i++) {
    var currentChar = valueString[valueString.length - 1 - i];

    if (i !== 0 && i % 3 === 0 && currentChar !== '-') {
      formattedValue = thousandsSeparator + formattedValue;
    }
    formattedValue = currentChar + formattedValue;
  }

  return formattedValue + decimalString + ' ' + currencyName;
}

Another approach would be to simply use the toLocaleString() function.
Probably the easiest and best solution:

function formatNumberAsCurrencyEuro_Locale(number){
  return number.toLocaleString('de-DE', {
    style: 'currency', 
    currency: 'EUR', 
    currencyDisplay: 'name',
    minimumFractionDigits: 0
  });
}

Funnily, even this function has the erroneous test case of -0 as an input.

Whole jsfiddle with running tests:
jsfiddle.net/tobiobeck/p35ebLvs/

Collapse
 
integerman profile image
Matt Eland

Love to see embracing testing! I fell in love with it in my first job, then went away from it to my detriment when I went to a UI-heavy job.

Take a look at Jest snapshot tests when you have some time. They're good for pinning down a wide variety of state without a specific test. I use them for stabilizing a tested environment as I'm adding more specific tests.

You also might want to look into parameterized testing or splitting tests out into multiple tests so that it's not an "all or nothing" type of approach with passes / failures.

Keep on writing!

Some comments may only be visible to logged-in visitors. Sign in to view all comments.