JavaScript

Getting Comfortable With Jest Mocks: A Practical Primer

Mocking sounds like one of those testing words everyone nods along to and nobody really wants to explain. If you have ever copied a `jest.mock` snippet from a Stack Overflow answer and hoped for the best, you are not alone. The official Jest documentation makes mocking harder than it needs to be.

We rebuilt the core concepts from the ground up for teams who test as part of their sprint rhythm, not as a chore at the end. Think of this as the cheat sheet you wished came in the box.

Quick take

  • The reference is a developer's notes on how Jest's mocking API behaves in real code, not a formal Jest manual.
  • It walks through the classic case: testing logic that depends on an external API without making real network calls.
  • It explains that `jest.mock` calls are hoisted above imports, which is why mock order looks "upside down."
  • It covers automock defaults, replacing return values, partial mocks with factory functions, and three cleanup levels: clear, reset, and restore.
  • It flags a TypeScript gotcha: wrap mocks in `jest.mocked()` so your compiler stays happy.

Why mocking trips people up in the first place

JavaScript modules are imported, not injected, so to swap a real module for a fake you have to grab Jest by the collar and tell it directly. That is what `jest.mock('api')` does. From that point on, any import of `'api'` gets a stand-in instead of the real thing.

The catch is hoisting. Jest lifts every `jest.mock` call to the top of the file before any import runs. So the order you see in the source is a polite fiction. This is also why you cannot rely on a constant defined just above your mock call; the runtime sees mocks first, constants second. Have you ever stared at a mock that ignored a value and assumed the universe was broken? It probably was not. It was hoisting.

What the default mock actually gives you

By default, Jest "automocks" a module. Functions become `jest.fn()` placeholders, and Jest walks the object tree, so nested functions get mocked too. Plain values such as strings and numbers are left alone. There is no tidy mapping table from real type to simulated behavior, so when you hit something unusual, trial and error is the honest answer.

That default is great at keeping tests from crashing. It is less great when your test wants a specific return value. The fix is to reach for `mockReturnValue`, `mockResolvedValue`, or `mockImplementation`. Pick based on what you need back: a constant, a promise, or a small piece of logic.

Partial mocks: keep the real, fake only what you must

Sometimes you do not want the whole module replaced, just one method swapped. Pass a factory function as the second argument to `jest.mock` and combine it with `jest.requireActual`, which pulls in the real module. Spread it, then override the one piece you care about.

Tests get brittle when every collaborator is faked. Keeping the genuine implementation for stable code paths and mocking only the edge you are testing usually makes failures easier to read. Factory functions are powerful but can drift from the real module's shape, so use them only when you need to.

Cleaning up between tests

Mocks leak. If your Monday test sets a return value and your Tuesday test does not, Tuesday inherits Monday's setup. Jest offers three cleanup levels:

  • Clear**: wipes call metadata such as how many times a mock was invoked
  • Reset**: does that and drops any stubbed return values
  • Restore**: goes the whole way and puts the original implementation back

You can target a single function with `myMock.mockClear`, `mockReset`, or `mockRestore`, or hit everything at once with `clearAllMocks`, `resetAllMocks`, and `restoreAllMocks`. If a module keeps state internally, `jest.resetModules()` clears the module cache so the next import starts fresh.

Decision checklist: which mock approach for which situation?

Situation Approach
Need to prevent network calls in a component test `jest.mock('module')` with automock
Need a specific return value Add `.mockReturnValue()` or `.mockResolvedValue()`
Need to simulate an error or custom logic `.mockImplementation()`
TypeScript project Wrap with `jest.mocked()` first
Only one method should be fake Factory function + `jest.requireActual()`
Test isolation failing between specs Add `beforeEach` with appropriate cleanup level
Module has internal state to clear Add `jest.resetModules()`

Our analysis

Mocking is one of the few areas where JavaScript testing genuinely rewards explicit intent. The trade-off is that the API surface is wide, and the defaults are forgiving enough to hide mistakes until production. Teams that document a shared "mocking style" in their repo tend to onboard new developers faster, because the choices are already made.

What to watch: whether your team reaches for `restore` consistently, or quietly switches to `reset` because it is shorter. That single habit is a reliable indicator of how seriously your test suite is maintained.

Bottom line

You do not need to master every Jest mocking corner to ship reliable code. Learn the three cleanup levels, understand hoisting, and reach for partial mocks only when automock is not enough. Do that, and your next sprint demo will not depend on a flaky network call.

Key takeaways

  • Treat hoisting as the rule, not the exception, when reading or writing `jest.mock` calls.
  • Use automock as the default and override return values only for the branch you are testing.
  • Standardise a cleanup level across your test suite so test order does not matter.
  • Reach for factory functions and `jest.requireActual` only when partial mocking is genuinely needed.
  • For TypeScript projects, wrap mocks in `jest.mocked()` to keep the compiler on your side.

Frequently asked questions

What is the difference between automock and a manual mock in Jest?

Automock is the default behaviour Jest applies when you call `jest.mock` without a factory: functions become `jest.fn()` placeholders while plain values are left alone. A manual mock is a hand-written replacement, usually passed in as a factory function, that defines exactly what the fake module returns.

Why do my `jest.mock` calls seem to run before my imports?

Because Jest hoists every `jest.mock` call to the top of the file before any import is resolved. The order you see in the source code is not the order the runtime sees, which is also why constants defined above a mock call may not be available inside it.

When should I use `mockClear`, `mockReset`, or `mockRestore`?

Use `clear` to wipe call history, `reset` to also drop any stubbed return values, and `restore` to put the original implementation back. Pick the lightest one that still keeps your tests independent, and apply it consistently.

How do I mock only one function from a module?

Pass a factory function as the second argument to `jest.mock`, spread `jest.requireActual('module')` into the returned object, and override just the function you want to fake. The rest of the module keeps its real behaviour.

I use TypeScript and my mocks show errors. What is the fix?

Wrap the mocked function in `jest.mocked()` before adding behaviour to it. That helper adds the type information Jest strips away, so your compiler stops complaining.

Related