Home >Web Front-end >JS Tutorial >A Beginner's Guide to Testing Functional JavaScript
Key Points
Functional programming and testing. You may have tried them separately, but somehow you never incorporated both into your regular practice. They may sound simple on their own, but testing and functional programming can create an irresistible temptation that almost forces you to write clearer, more compact, and more maintainable code.
The good news is that using both technologies simultaneously provides some practical advantages. In fact, once you experience the sweetness of this combination, you may be addicted to it like me, and I bet you'll want more.
Watch the "Writing Clear Pure Code Follow Functional JavaScript Principles" course to break down your code and make it easier to maintain... Why not? Watch this course In this article, I will introduce you to the principles of testing functional JavaScript. I'll show you how to use the Jasmine framework and build pure functions using test-driven methods.
Why test it?
Testing is to ensure that the code in the application runs as expected and continues to run as expected when you make changes so that you get the available products when you finish your work. You write a test that defines the functionality expected in a defined set of situations, run the test against the code, and you will receive a warning if the result does not match what the test says. And you keep getting that warning until you fix the code.
Then you will get a reward.
Yes, this will make you feel good.
There are many types of tests, and there is a lot of room for discussion about where boundaries are divided, but in short:
Note: Don't be distracted by a type of test called functional test. This is not what we want to focus on in this article about testing functional JavaScript. In fact, whether you use functional programming techniques in JavaScript or not, the functional testing methods you use to test the overall behavior of your application may not change much. What really helps in functional programming is when building unit tests.
You can write tests any time during the encoding process, but I always find it most effective to write unit tests before writing functions that plan 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 part of your code, first write the test, and then write the code to produce that result.
One side benefit is that TDD often forces you to have detailed discussions with the people who pay you to write programs to make sure the program you are writing is indeed the one they are looking for. After all, it's easy to get a single test to pass. The difficulty is to determine whether to process all possible inputs that you may encounter and to process all inputs correctly without breaking anything.
Why choose functional programming?
As you can imagine, the way you write your code is closely related to the difficulty of testing. Some code patterns, such as tightly coupling the behavior of one function to another, or relying heavily on global variables, make unit testing of the code more difficult. Sometimes, you may have to use inconvenient techniques such as “simulating” the behavior of an external database or simulating a complex runtime environment in order to establish testable parameters and results. These cases are not always avoidable, but it is often possible to isolate where these cases are needed in the code so that the rest of the code can be tested more easily.
Functional programming allows you to handle data and behavior in your application independently. You can build your application by creating a separate set of functions that work independently and do not depend on external state. As a result, your code becomes almost self-documented code, combining small, clearly defined functions that are consistently behaving and easy to understand.
Functional programming is usually in contrast to imperative programming and object-oriented programming. JavaScript can support all these technologies, and even mix them. Functional programming can serve as another valuable alternative to creating imperative code sequences that track application states until the result is returned. Or build your application through interactions across complex objects that encapsulate all methods that apply to a specific data structure.
How to work pure functions
Functional programming encourages you to build your application using tiny, reusable, composable functions that do only one specific thing and return the same value for the same input each time. Such functions are called pure functions. Pure functions are the basis of functional programming, and they all have the following three characteristics:
Another advantage of writing functional code is that it makes unit testing easier. The more code you can unit test, the easier it will be to rely on your ability to refactor your code in the future without breaking the basic functionality.
What makes functional code easy to test?
If you consider the concept we just discussed, you may already understand why functional code is easier to test. Writing tests for pure functions is very simple because each input has a consistent output. You just set the expected values and run them against the code. There is no need to establish a context, no need to track dependencies between functions, no need to simulate the change state outside the function, nor does it need to simulate the external data source of variables.
There are many testing options, from complete frameworks to utility libraries and simple testing tools. These include Jasmine, Mocha, Enzyme, Jest and many other tools. Each tool has different pros and cons, best use cases and a loyal following. Jasmine is a powerful framework that can be used in a variety of situations, so here is a quick demonstration of how to develop pure functions using Jasmine and TDD in your browser.
You can create an HTML document that extracts Jasmine test libraries from local or CDN. An example of a page containing the Jasmine library and test runner might look like this:
<code class="language-html"><!DOCTYPE html> <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"> </code>
This introduces the Jasmine library, as well as Jasmine HTML boot scripts and styles. In this case, the body of the document is empty, waiting for your JavaScript to be tested and your Jasmine tests.
Test Functional JavaScript — Our First Test
First, let's write the first test. We can do this in a separate document or by including it in the <script></script>
element on the page. We will use the describe
function defined by the Jasmine library to describe the desired behavior of a new function that we have not written yet.
The new function we will write will be called isPalindrome
, and if the string passed in is the same before and after, it will return true
, otherwise it will return false
. The test will look like this:
<code class="language-javascript">describe("isPalindrome", () => { it("returns true if the string is a palindrome", () => { expect(isPalindrome("abba")).toEqual(true); }); });</code>
When we add this to the script in the page and load it into the browser, we get a working Jasmine report page showing the error. This is what we want at this moment. We want to know if the test is running and if it fails. In this way, our brains that are eager to get recognized know that we need to fix something.
So let's write a simple function in JavaScript where there is only enough logic to get our tests to pass. In this case, it is just a function that makes one of our tests pass by returning the expected value.
<code class="language-html"><!DOCTYPE html> <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"> </code>
Yes, really. I know it looks ridiculous, but please stick with it.
When you run the test run program again, it will pass. certainly. But obviously, this simple code does not meet our expectations for the palindrome test program. What we need now is the extra expectation. So let's add another assertion to our describe
function:
<code class="language-javascript">describe("isPalindrome", () => { it("returns true if the string is a palindrome", () => { expect(isPalindrome("abba")).toEqual(true); }); });</code>
Reloading our page now causes the test output to turn red and fail. We received a message stating what the problem was and the test result turned red.
Red!
Our brain feels something is wrong.
Of course there is a problem. Now, our simple isPalindrome
function returns only true
every time, and has proven to be unable to effectively target this new test. So let's update isPalindrome
and add the ability to compare the functions before and after the incoming string.
<code class="language-javascript">const isPalindrome = (str) => true;</code>
Tests are addictive
Go green again. This is satisfying. When you reload the page, do you get that little dopamine surge?
With these changes, our test passes again. Our new code effectively compares the before and after strings and returns true
when the before and after strings are the same, otherwise false
.
This code is a pure function because it does only one thing and is consistently executed given a consistent input value without any side effects, changing any variables or dependencies outside of itself or in the application status. Every time you pass a string to this function, it compares between the front and back strings and returns the result regardless of when or how it is called.
You can see how easy this consistency makes unit testing of this function. In fact, writing test-driven code can encourage you to write pure functions because they are easier to test and modify.
And you want to get the satisfaction of passing the test. I know you will.
Reconstructing pure function
At this point, it is very simple to add other functions (such as handling non-string input, ignoring differences between upper and lower case letters, etc.). Just ask the product owner how they want the program to run. Since we already have tests to verify that the string will be processed consistently, we can now add error checking or string casting or any behavior we like to non-string values.
For example, let's see what happens if we add a test for the number 1001, which may be interpreted as palindrome if it is a string:
<code class="language-javascript">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); }); });</code>
Doing this will make our screen red again and cause the test to fail because our current isPalindrome
function does not know how to handle non-string input.
The panic began. We see red. The test failed.
But now we can safely update it to handle non-string inputs, cast them to strings and then check. We might come up with a function that looks more like this:
<code class="language-html"><!DOCTYPE html> <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"> </code>
Now that all the tests are passed, we see green, sweet dopamine pouring into our test-driven brain.
By adding toString()
to the evaluation chain, we are able to adapt to non-string input and convert it to a string before testing. Best of all, since our other tests are run every time, we can be sure that by adding this new feature to our pure functions, we are not breaking the functionality we have previously obtained. The final result is as follows:
(The CodePen code should be inserted here, but since I can't access the external website, I can't provide the CodePen link.)
Try this test and start writing your own tests, using Jasmine or any other test library that works for you.
Once you incorporate tests into your code design workflow and start writing pure functions for unit testing, you may find it difficult to go back to your past life. But you will never want to do that.
(This article was peer-reviewed by Vildan Softic. Thanks to all SitePoint peer reviewers for getting SitePoint content to its best!)
FAQs about functional JavaScript testing (FAQ)
Functional JavaScript testing is a test that checks the functions of various parts of the code (such as functions) to make sure they work as expected. This involves writing test cases that call functions using different inputs and compare the output to the expected results. It is a crucial part of software development and helps to detect errors in the development process as early as possible and ensures the quality and reliability of the code.
Writing functional tests in JavaScript involves creating a test case that calls the function to be tested using a specific input and then checks if the output matches the expected result. This can be done using various testing frameworks, such as Jest, Mocha, or Jasmine. These frameworks provide functions to define test cases, run them, and check results.
The RegExp test method in JavaScript is a way to test matches in a string. Returns true
if a match is found, otherwise returns false
. This method is especially useful when you need to verify user input or search for patterns in strings.
To use the RegExp test method in JavaScript, you first need to create a RegExp object using the pattern you want to match. You then call on the test method of this object, passing the string to be tested as a parameter. If a match is found, the method returns true
, otherwise false
.
Functional testing and unit testing are both important components of the testing process, but their purpose is different. Unit testing focuses on isolating various parts of the test code (such as functions or methods) and isolating from the rest of the system. Functional testing, on the other hand, tests the system's larger chunks of features (such as modules or entire applications) to ensure they work as expected when integrated.
Improving Functional testing in JavaScript usually involves ensuring that your tests cover all possible situations, including extreme situations and potential error conditions. It is also important to make sure your tests are independent and can be run in any order, as this makes them more reliable and easier to maintain. Using a test framework can also help build tests and provide useful tools to check results and report errors.
Some good practices for functional testing in JavaScript include writing clear and concise test cases, testing positive and negative scenarios, and ensuring that the tests are independent and can run in any order. Using a test framework is also a good idea as it provides useful tools and structures for your tests.
A variety of tools and techniques can be used to debug functional tests in JavaScript. A common method is to use the console.log
statement to print out the value during test execution. You can also use debugging tools provided by the development environment or test frameworks that allow you to step through the code and check variables at different points in time.
Some common challenges in functional testing in JavaScript include handling asynchronous code, testing code that interacts with external systems, and managing the complexity of large test suites. A variety of technologies and tools can be used to solve these challenges, such as using Promise or async/await
for asynchronous code, simulating external systems, and building tests in an easy to understand and maintain.
There are many resources to learn more about functional testing in JavaScript. This includes online tutorials, books and courses. Websites such as 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. In addition, many JavaScript testing frameworks such as Jest, Mocha, and Jasmine provide detailed documentation and guidance on their official website.
The above is the detailed content of A Beginner's Guide to Testing Functional JavaScript. For more information, please follow other related articles on the PHP Chinese website!