Can You Refactor JavaScript Safely Without Test Coverage?

Lars Grammel
Lars Grammel
Published on October 24, 2021

The conventional wisdom is that you should have good test coverage before refactoring your code. Making mistakes during refactoring will introduce defects in the software if they are not caught during testing. Such regression defects can annoy users, break the software and even lead to customers abandoning the product.

The need for automated testing is especially high for JavaScript. In a dynamic language with functional constructs like JavaScript, it is harder to detect breakages statically. Additionally, JavaScript has many language details and quirks that are not well known or easy to remember.

In practice, however, there is a chance that you will encounter situations where you need to work in untested or insufficiently tested legacy code. Often this is precisely the kind of code that would benefit from refactoring to make it easier to understand and extend. But to test it, we need to make it testable, which involves refactoring.

How do we resolve this problem? If we take a step back, the underlying question that automated testing helps us answer is a question of confidence and risk:

How confident am I that my refactoring has not changed the program behavior?

Tests help us answer the question, but there is a chance that refactoring introduces bugs when there are gaps in test coverage. Therefore the answer to the question is a matter of degree, even with tests.

Another approach is taking a sequence of small steps. If we are confident that each step is correct, the final result is correct since no step changes the program behavior. Ideally, taking small, low-risk steps is combined with excellent test coverage, leading to high confidence.

However, significant changes, a more complex programming language like JavaScript, and complex legacy codebases lead to a high risk that individual steps introduce defects. In particular, refactorings that span module or service boundaries, large-scale renames where not every symbol occurrence might get picked up, or extensive manual changes tend to be error-prone.

But for single functions, classes or modules, it can be possible to chain together sequences of automated refactorings to achieve a more extensive refactoring. However, most refactoring tools ensure that the refactoring mechanics are correctly executed, but they do not tell you about the impact on your code.

I wanted a refactoring tool with built-in knowledge of many hard-to-remember JavaScript details that can analyze the relevant code to evaluate the safety of a refactoring. With this in mind, I created the P42 JavaScript Assistant for Visual Studio Code. When possible, P42 evaluates the refactoring impact on the logic and the flow of your code (excluding performance) and informs you if it is safe or if specific details need checking.

Here is an example of how P42 indicates the safety of small refactoring steps: P42 Refactoring Example

With automatic refactoring tools such as P42 that combine static analysis of the relevant source code with extensive knowledge of the programming language, it is possible to refactor code more safely. In combination with automated testing and type checking tools like TypeScript, this makes it possible to refactor with high confidence even in a dynamic language like JavaScript.

Happy Refactoring!