Mocking Breaks Refactoring

I’ve had members of the team raise the concern to me about getting things perfect now because they’ll be hard to refactor later.

This can be true, but it shouldn’t be hard to refactor code that’s all within the same repository and project. Indeed, I usually encourage folks to get things approximately right now, in the spirit of simplicity, and then refactor it later when the circumstances change.

However, I recently did a refactoring that threatened to make things as hard as they shouldn’t be.

We had this file structure:

/lib
  signer.ts
  ... loads of other utils

I had just made a file called headerSigning.ts (or something like that) and it was related to signer.ts. So I did this refactoring:

/lib
  /signing
    headerSigning.ts
    signer.ts
  ... loads of other things

It’s a thing I like to do when some sibling files seem to more strongly relate to each other – put them into their own category.

The IDE did a good job of rewiring the dependencies… or so we thought.

We were running the tests in watch mode, but the watching didn’t notice the effect of this refactoring. A couple of minutes later, when I deliberately forced a re-run of all tests, a bunch of stuff went bang.

The root cause was something like this:

// test file

vi.mock('../../src/lib/signer', () => ({
  sign: vi.fn(() => Promise.resolve('signed'),
});

This mocking statement needs to know the correct path of the module it’s mocking. The refactoring tool doesn’t understand that the string used in vi.mock is somehow a part of the dependency network. So, bang.

This shows us some edge-case limitations of refactoring tools, and the consequences of choosing to mock things in this sort of a way (which seems all too common for JavaScript/TypeScript, without a satisfactory alternative).

My view is that these are a relatively small price to pay for an occasional refactoring. They should be done under continuous (per change of code) re-running of all relevant tests, so we can quickly spot the consequence of the last refactor and know where to look to sort it out.

Similarly, we should take the opportunity to rethink our approach to mocking in cases where one of these goes bang. Maybe we’ve hit a max limit on this technique? or maybe it’s a one-off.

Leave a comment