node.js – How to avoid messy variable dependencies for ease of testing in Node?

I couldn’t decide if the question belongs on Stack Overflow, but I went with SE Stack Exchange for the higher quality, broader answers. Bare with me.

Examples are in Node but I guess applies to a multitude of languages

I’m big on writing pure code without side effects. It’s easy to reason about and to test. Yet, the following situation has annoyed me for years, and I need a resolution, as I can’t quite solve it definitively.

For a Node module (really any self-contained unit of code), I always envision something like this:

const yourThing = {
  doYourThing: (params) => {
    // A bunch of function calls, all pure,
    // All beautifully harmonious

const fun1 = (params) => ...
const fun2 = (params) => ...
const fun3 = (params) => ...

module.exports = yourThing

But it always devolves into…

const const1 = 0
const const2 = 1
const someLoadBearingBool = false;

let ChangingVariable1;
let ChangingVariable2;
let ChangingVariable3;

// big and complex
let someState;

const yourThing = {
  doYourThing: (params) => {
    // Assign changing variables
    someState = { param1, ...};

// All these are changing the module-global state;
const dirtyfun1 = (params) => ...
const dirtyfun2 = (params) => ...
const dirtyfun3 = (params) => ...

module.exports = yourThing

The dirty functions are not easy to test. Sometimes the order in which you run them matters, as they keep switching variable values on each other.

Sometimes I can’t change the signature of these functions, as they must be so by contract, used elsewhere, so injections are not really viable, without major overhaul.

It makes it very annoying to write good tests, and hard to keep track of what’s going on.

I see the shadow of this problem in almost all my code, it’s almost like there’s a bug in my thinking and I feel it seep into larger structures (like weird dependencies showing up across the entire software). Also it slows down my productivity, as I sometimes end up working waaay too hard/long to minimize this.

(I’d also like to avoid depending on libraries like rewire, as this introduces an extra dependency to your project, and one specifically to fix something that (in my mind) shouldn’t be broke.)

I have yet to find a general solution to this.

What are some good techniques and methods to mitigate this to the greatest extent possible? How do you suppress your annoyance if this is unavoidable?