0

A test in our frankly very complicated React app is taking ~1000 times longer to run than expected, and I'm trying to pin down where/why.

I started by manually calling console.time('name of test') in the test file, and then manually dotting console.timeLog('name of test', 'did a thing') around the application in all the places that were called and seemed likely to cause slow downs.

I noticed a lot of these places were inside React hooks - they aren't slow themselves, but were helping me see how long it took for coffee to get there.

I decided I needed to write a monkey patch in a Jest setupFilesAfterEnv file for logging when React hooks callback are called, and what with (for this example, I'll use useEffect)

const React = require('react');

let timeTestName = null;
let doTimeLog = false;
let prevUseEffect;

beforeAll(() => {
  ({ timeTestName } = global);
  const prevUseEffect = React.useEffect;
  React.useEffect = (cb, deps) => {
    if(doTimeLog && timeTestName && Array.isArray(deps) && !__filename.includes('node_modules')){
      console.timeLog(timeTestName, `Use Effect called with ${JSON.stringify(deps, null, 2)}`): // log useEffect being called with timer
    }
    prevUseEffect(cb, deps);
  }
});

beforeEach(() => {
  const { testPath } = expect.getState();  
  if(testPath.endsWith(`${timeTestName}.test.js`)) {
    doTimeLog = true;
    console.time(timeTestName); // start timer
  } else {
    doTimerLog = false;
  }
});

afterEach(() => {
  doTimerLog = false;
  console.log(testToTimeName); // end timer
});

afterAll(() => {
  React.useEffect = prevUseEffect;
})

However what I really want as well is the variable names in the dependency list, which I cannot get (at least not without changing non-test code).

One thought I had was using a Jest transformer to make all the arrays into objects so I preserve the variable names as keys; something like:


module.exports = {
  process(sourceText) {
    return {
      code: `convertSquareToCurly(sourceText)`,
    };
  },
};

module.exports = {
  transform: {
    'matchDepList':
      '<rootDir>/deplistTransformer.js',
  },
};

So during tests only my useEffects become:

useEffect(() => foo(a, b, c), { a, b, c})

(I will handle this new object in my monkey patch)

I'm reasonably confident I can make the above work.

Then I in my monkey patch I can call console.log(Object.entries(deps)); and prevUseEffect(cb, Object.values(deps));.

However if I'm concerned that calling Object.values will cause the hook's callback to always be called.

I haven't been able to try the transformer yet, but I don't want to waste time writing it if passing Object.values won't work in place of the untransformed dependency list.

Is there another way to monkey patch and get variable names, or would this work?

22
  • 1
    What's the context - why do you think you need this? Interfering with interfaces you don't own is generally a test/design smell. Commented May 20, 2022 at 18:03
  • What's wrong with just passing the object inside that dependency array? I don't see a scenario where this wouldn't work. Keep things standard man as per React interfaces. Commented May 20, 2022 at 18:07
  • 1
    @MosiaThabo do you mean like [{ foo, bar, baz }] instead of [foo, bar, baz]? That would be a new, unequal object on every render and therefore trigger the effect every time, you might as well have no deps at that point. Commented May 20, 2022 at 18:11
  • 1
    @AncientSwordRage what they said was "object inside the dependency array". React's expecting something with a length and maybe a 0th, 1th, etc. value, so you certainly can't just pass an object. I guess you could pass something that has both though, indexed and named values. But again this is probably all in entirely the wrong direction. Commented May 20, 2022 at 18:20
  • 1
    While you've already been up and down this tree, I can't help but feel that this is excessive. I would wager it's considerably easier to globally find and replace useEffect() with a-la useEffectDebugger() and go from there if you really want to see all hooks firing. Commented May 20, 2022 at 19:15

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.