A Beginner’s Guide to Testing Functional JavaScript

Share this article

A Beginner’s Guide to Testing Functional JavaScript

Functional programming and testing. Maybe you’ve given them a try in isolation, but somehow you never made either a part of your regular practice. They may sound innocent by themselves, but together testing and functional programming can create an irresistible temptation, almost compelling you to write cleaner, tighter, more maintainable code.

Testing Functional JavaScript — A crash test dummy sits at a computer with a mug of coffee

Well the good news is that working with both techniques together can offer some real advantages. In fact, once you’ve tasted how sweet this combination can be, you might find yourself as addicted to it as I am, and I’d be willing to bet that you’ll be coming back for more.

In this article, I’ll introduce you to the principles of testing functional JavaScript. I’ll show you how to get up and running with the Jasmine framework and build out a pure function using a test-driven approach.

Why Test?

Testing is about making sure that the code in your application does what you expect it to do, and keeps doing what you expect it to do when you make changes so you have a working product when you’re done. You write a test that defines your expected functionality under a defined set of circumstances, run that test against the code, and if the result isn’t what the test says it should be, you get a warning. And you keep getting that warning until you’ve fixed your code.

Then you get the reward.

And yes, it will make you feel good.

Testing comes in a lot of flavors, and there is room for healthy debate about where the borders are drawn, but in a nutshell:

  • Unit tests validate the functionality of isolated code
  • Integration tests verify the flow of data and the interaction of components
  • Functional tests look at the behavior of the overall application

Note: Don’t get distracted by the fact that there’s a type of testing called functional testing. That’s not what we’ll be focusing on in this article about testing functional JavaScript. In fact, the approach you’ll use for functional testing of the overall behavior of an application probably won’t change all that much whether or not you’re using functional programming techniques in your JavaScript. Where functional programming really helps out is when you’re building your unit tests.

You can write a test at any point in the coding process, but I’ve always found that its most efficient to write a unit test before writing the function you’re planning to test. This practice, known as test-driven development (TDD), encourages you to break down the functionality of your application before you start writing and determine what results you want from each section of code, writing the test first, then coding to produce that result.

A side benefit is that TDD often forces you to have detailed conversations with the people who are paying you to write your programs, to make sure that what you’re writing is actually what they’re looking for. After all, it’s easy to make a single test pass. What’s hard is determining what to do with all the likely inputs you’re going to encounter and handle them all correctly without breaking things.

Why Functional?

As you can imagine, the way you write your code has a lot to do with how easy it is to test. There are some code patterns, such as tightly coupling the behavior of one function to another, or relying heavily on global variables, that can make code much more difficult to unit test. Sometimes you may have to use inconvenient techniques such as “mocking” the behavior of an external database or simulating a complicated runtime environment in order to establish testable parameters and results. These situations can’t always be avoided, but it is usually possible to isolate the places in the code where they are required so that the rest of the code can be tested more easily.

Functional programming allows you to deal with the data and the behavior in your application independently. You build your application by creating a set of independent functions that each work in isolation and don’t rely on external state. As a result, your code becomes almost self-documenting, tying together small clearly defined functions that behave in consistent and understandable ways.

Functional programming is often contrasted against imperative programming and object-oriented programming. JavaScript can support all of these techniques, and even mix-and-match them. Functional programming can be a worthwhile alternative to creating sequences of imperative code that track the state of the application across multiple steps until a result is returned. Or building your application out of interactions across complex objects that encapsulate all of the methods that apply to a specific data structure.

How Pure Functions Work

Functional programming encourages you to build your application out of tiny, reusable, composable functions that just do one specific thing and return the same value for the same input every single time. A function like this is called a pure function. Pure functions are the foundation of functional programming, and they all share these three qualities:

  • Don’t rely on external state or variables
  • Don’t cause side effects or alter external variables
  • Always return the same result for the same input

Another advantage of writing functional code is that it makes it much easier to do unit testing. The more of your code that you can unit test, the more comfortably you can count on your ability to refactor the code in the future without breaking essential functionality.

What Makes Functional Code Easy to Test?

If you think about the concepts that we just discussed, you probably already see why functional code is easier to test. Writing tests for a pure function is trivial, because every single input has a consistent output. All you have to do is set the expectations and run them against the code. There’s no context that needs to be established, there are no inter-functional dependencies to keep track of, there’s no changing state outside of the function that needs to be simulated, and there are no variable external data sources to be mocked out.

There are a lot of testing options out there ranging from full-fledged frameworks to utility libraries and simple testing harnesses. These include Jasmine, Mocha, Enzyme, Jest, and a host of others. Each one has different advantages and disadvantages, best use cases, and a loyal following. Jasmine is a robust framework that can be used in a wide variety of circumstances, so here’s a quick demonstration of how you might use Jasmine and TDD to develop a pure function in the browser.

You can create an HTML document that pulls in the Jasmine testing library either locally or from a CDN. An example of a page including the Jasmine library and test runner might look something like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Jasmine Test</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/jasmine.min.css">
  </head>
  <body>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/jasmine.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/jasmine-html.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/boot.min.js"></script>
  </body>
</html>

This brings in the Jasmine library, along with the Jasmine HTML boot script and styling. In this case the body of the document is empty, waiting for your JavaScript to test, and your Jasmine tests.

Testing Functional JavaScript — Our First Test

To get started, let’s write our first test. We can do this in a separate document, or by including it inside a <script> element on the page. We’re going to use the describe function defined by the Jasmine library to describe the desired behavior for a new function we haven’t written yet.

The new function we’re going to write will be called isPalindrome and it will return true if the string passed in is the same forwards and backwards, and return false otherwise. The test will look like this:

describe("isPalindrome", () => {
  it("returns true if the string is a palindrome", () => {
    expect(isPalindrome("abba")).toEqual(true);
  });
});

When we add this to a script in our page and load it a browser, we get a working Jasmine report page showing an error. Which is what we want at this point. We want to know that the test is being run, and that it’s failing. That way our approval-hungry brain knows that we have something to fix.

So let’s write a simple function in JavaScript, with just enough logic to get our test to pass. In this case it’s just going to be a function that makes our one test pass by returning the value expected.

const isPalindrome = (str) => true;

Yes, really. I know it looks ridiculous, but hang in there with me.

When the test runner again, it passes. Of course. But obviously this simple code does not do what we might expect a palindrome tester to do. We’ve written the minimal amount of code that makes the test pass. But we know that our code would fail to evaluate palindromes effectively. What we need at this point are additional expectations. So let’s add another assertion to our describe function:

describe("isPalindrome", () => {
  it("returns true if the string is a palindrome", () => {
    expect(isPalindrome("abba")).toEqual(true);
  });
  it("returns false if the string isn't a palindrome", () => {
    expect(isPalindrome("Bubba")).toEqual(false);
  });
});

Reloading our page now makes the test output turn red and fail. We get a messages saying what the problem is, and the test result turns red.

Red!

Our brains sense that there’s a problem.

Of course there is. Now our simple isPalindrome function that just returns true every time has been demonstrated not to work effectively against this new test. So let’s update isPalindrome adding the ability to compare a string passed in forward and backward.

const isPalindrome = (str) => {
  return str
    .split("")
    .reverse()
    .join("") === str;
};

Testing Is Addictive

Green again. Now that’s satisfying. Did you get that little dopamine rush when you reloaded the page?

With these changes in place, our test passes again. Our new code effectively compares the forward and backward string, and returns true when the string is the same forward and backward, and false otherwise.

This code is a pure function because it is just doing one thing, and doing it consistently given a consistent input value without creating any side effects, making any changes to variables outside of itself, or relying on the state of the application. Every time you pass this function a string, it does a comparison between the forward and backward string, and returns the result regardless of when or how it is called.

You can see how easy that kind of consistency makes this this function to unit test. In fact, writing test-driven code can encourage you to write pure functions because they are so much easier to test and modify.

And you want the satisfaction of a passing test. You know you do.

Refactoring a Pure Function

At this point it’s trivial to add additional functionality, such as handling non-string input, ignoring differences between uppercase and lowercase letters, etc. Just ask the product owner how they want the program to behave. Since we already have tests in place to verify that strings will be handled consistently, we can now add error checking or string coercion or whatever behavior we like for non-string values.

For example, let’s see what happens if we add a test for a number like 1001 that might be interpreted a palindrome if it were a string:

describe("isPalindrome", () => {
  it("returns true if the string is a palindrome", () => {
    expect(isPalindrome("abba")).toEqual(true);
  });
  it("returns false if the string isn't a palindrome", () => {
    expect(isPalindrome("Bubba")).toEqual(false);
  });
  it("returns true if a number is a palindrome", () => {
    expect(isPalindrome(1001)).toEqual(true);
  });
});

Doing this gives us a red screen and a failing test again because our current is isPalindrome function doesn’t know how to deal with non-string inputs.

Panic sets in. We see red. The test is failing.

But now we can safely update it to handle non-string inputs, coercing them into strings and checking them that way. We might come up with a function that looks a little bit more like this:

const isPalindrome = (str) => {
  return str
    .toString()
    .split("")
    .reverse()
    .join("") === str.toString();
};

And now all our tests pass, we’re seeing green, and that sweet, sweet dopamine is flooding into our our test-driven brains.

By adding toString() to the evaluation chain, we’re able to accommodate non-string inputs and convert them to strings before testing. And best of all, because our other tests are still being run every time, we can be confident that we haven’t broken the functionality we got before by adding this new capability to our pure function. Here’s what we end up with:

See the Pen A Beginner’s Guide to Testing Functional JavaScript by SitePoint (@SitePoint) on CodePen.

Play around with this test, and start writing some of your own, using Jasmine or any other testing library that suits you.

Once you incorporate testing into your code design workflow, and start writing pure functions to unit test, you may find it hard to go back to your old life. But you won’t ever want to.

This article was peer reviewed by Vildan Softic. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Frequently Asked Questions (FAQs) about Functional JavaScript Testing

What is Functional JavaScript Testing?

Functional JavaScript testing is a type of testing that checks the functionality of individual pieces of code, such as functions, to ensure they work as expected. This involves writing test cases that call the function with different inputs and comparing the output with the expected results. It’s a crucial part of software development that helps to catch bugs early in the development process, ensuring the quality and reliability of the code.

How do I write a functional test in JavaScript?

Writing a functional test in JavaScript involves creating a test case that calls the function you want to test with specific inputs and then checks if the output matches the expected result. This can be done using various testing frameworks like Jest, Mocha, or Jasmine. These frameworks provide functions to define test cases, run them, and check the results.

What is the RegExp test method in JavaScript?

The RegExp test method in JavaScript is a method that tests for a match in a string. It returns true if it finds a match, otherwise it returns false. This method is particularly useful when you need to validate user input or search for patterns within strings.

How do I use the RegExp test method in JavaScript?

To use the RegExp test method in JavaScript, you first need to create a RegExp object with the pattern you want to match. Then, you call the test method on this object, passing the string you want to test as an argument. The method will return true if it finds a match, and false otherwise.

What is the difference between functional testing and unit testing in JavaScript?

Functional testing and unit testing are both important parts of the testing process, but they serve different purposes. Unit testing is focused on testing individual pieces of code, like functions or methods, in isolation from the rest of the system. Functional testing, on the other hand, tests the functionality of larger pieces of the system, like modules or entire applications, to ensure they work as expected when integrated together.

How can I improve my functional tests in JavaScript?

Improving functional tests in JavaScript often involves making sure that your tests cover all possible scenarios, including edge cases and potential error conditions. It’s also important to make sure your tests are independent and can run in any order, as this makes them more reliable and easier to maintain. Using a testing framework can also help to structure your tests and provide useful tools for checking results and reporting errors.

What are some good practices for functional testing in JavaScript?

Some good practices for functional testing in JavaScript include writing clear and concise test cases, testing for both positive and negative scenarios, and making sure your tests are independent and can run in any order. It’s also a good idea to use a testing framework, as this can provide useful tools and structure for your tests.

How can I debug my functional tests in JavaScript?

Debugging functional tests in JavaScript can be done using various tools and techniques. One common approach is to use console.log statements to print out values during the execution of the test. You can also use debugging tools provided by your development environment or testing framework, which can allow you to step through your code and inspect variables at different points in time.

What are some common challenges in functional testing in JavaScript?

Some common challenges in functional testing in JavaScript include dealing with asynchronous code, testing code that interacts with external systems, and managing the complexity of large test suites. These challenges can be addressed using various techniques and tools, such as using promises or async/await for asynchronous code, mocking external systems, and structuring your tests in a way that makes them easy to understand and maintain.

How can I learn more about functional testing in JavaScript?

There are many resources available to learn more about functional testing in JavaScript. This includes online tutorials, books, and courses. Websites like W3Schools, Mozilla Developer Network, and Geeks for Geeks provide comprehensive guides and tutorials on the topic. You can also find many video tutorials on platforms like YouTube. Additionally, many JavaScript testing frameworks like Jest, Mocha, and Jasmine have detailed documentation and guides available on their official websites.

M. David GreenM. David Green
View Author

I've worked as a Web Engineer, Writer, Communications Manager, and Marketing Director at companies such as Apple, Salon.com, StumbleUpon, and Moovweb. My research into the Social Science of Telecommunications at UC Berkeley, and while earning MBA in Organizational Behavior, showed me that the human instinct to network is vital enough to thrive in any medium that allows one person to connect to another.

functional-jsjameshjasminemodernjs-tutorialspure functionsunit testing
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week