---
title: Mock intersections
description: Drive deterministic observer callbacks and thresholds in Vitest, Jest, or another DOM-capable test runner.
---

Use `react-intersection-observer/test-utils` when the assertion is about a callback, state transition, or threshold branch rather than real layout. The utilities replace `window.IntersectionObserver`, remember which elements each mock observer watches, and let a helper deliver controlled entries to the registered callback.

## Set up the mock

Importing `test-utils` in a DOM test environment can set the mock up for you. When the module finds global `beforeEach`, `afterEach`, and either `vi` or `jest`, it registers its own hooks. The `beforeEach` hook installs the observer mock with that runner's `fn`. The `afterEach` hook clears both the mock calls and the observed elements, so nothing carries between tests. Put the import in a setup file so every relevant test gets the same lifecycle.

Install a DOM emulator first. `happy-dom` is usually faster, and `jsdom` may fit an existing suite better. Neither one proves layout, scrolling, or native observer delivery.

```bash
pnpm add -D happy-dom
# or: pnpm add -D jsdom
```

```ts Vitest
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "happy-dom", // "jsdom" works too
    setupFiles: ["./test/setup.ts"],
  },
});

// test/setup.ts
import "react-intersection-observer/test-utils";
```

```js Jest + jsdom
// jest.config.js
module.exports = {
  testEnvironment: "jsdom",
  setupFilesAfterEnv: ["react-intersection-observer/test-utils"],
};
```

The mock does not care which runner you use. Jest is not tied to `jsdom`, and Vitest works with either `jsdom` or `happy-dom`. In a mixed Vitest suite, keep this setup in a `dom` project so it never reaches the [Browser Mode](/testing/browser-mode) tests.

### Manual setup without globals

If your runner does not expose global test APIs, or you prefer importing them, register the lifecycle yourself. `setupIntersectionMocking` takes the runner's mock factory, and `resetIntersectionMocking` clears it after every test.

```ts Vitest
import { afterEach, beforeEach, vi } from "vitest";
import {
  resetIntersectionMocking,
  setupIntersectionMocking,
} from "react-intersection-observer/test-utils";

beforeEach(() => setupIntersectionMocking(vi.fn));
afterEach(resetIntersectionMocking);
```

```ts Jest
import { afterEach, beforeEach, jest } from "@jest/globals";
import {
  resetIntersectionMocking,
  setupIntersectionMocking,
} from "react-intersection-observer/test-utils";

beforeEach(() => setupIntersectionMocking(jest.fn));
afterEach(resetIntersectionMocking);
```

## Drive a transition

With either setup in place, import a helper and trigger the observer state you need:

```tsx
import { render } from "@testing-library/react";
import { expect, test, vi } from "vitest";
import { useOnInView } from "react-intersection-observer";
import { mockAllIsIntersecting } from "react-intersection-observer/test-utils";

function Probe({ onSeen }: { onSeen: (inView: boolean) => void }) {
  const ref = useOnInView((inView) => onSeen(inView));
  return <div ref={ref}>Observed</div>;
}

test("ignores the initial false notification", () => {
  const onSeen = vi.fn();
  render(<Probe onSeen={onSeen} />);

  mockAllIsIntersecting(false);
  expect(onSeen).not.toHaveBeenCalled();

  mockAllIsIntersecting(true);
  expect(onSeen).toHaveBeenCalledWith(true);
});
```

Pass a boolean to enter or leave. A number selects which of the configured thresholds the observer has crossed, and the mock reports the highest crossed one as `entry.intersectionRatio`. Assert against the thresholds you configured.

These examples work the same in Vitest and Jest. Switch to [Browser Mode](/testing/browser-mode) when a test has to prove real browser behavior.

## Utilities

| Utility | Use |
| --- | --- |
| `mockAllIsIntersecting(value)` | Trigger every observed element. |
| `mockIsIntersecting(element, value)` | Trigger one observed element. |
| `intersectionMockInstance(element)` | Inspect the mock observer for an element. |
| `setupIntersectionMocking(mockFn)` | Install the mock yourself with `vi.fn`, `jest.fn`, or a compatible factory. |
| `resetIntersectionMocking()` | Clear observed elements and mock calls between tests. |
| `destroyIntersectionMocking()` | Restore the native observer after mocking it. |

:::note
The mock can drive a `trackVisibility` fallback branch, but it cannot prove real v2 occlusion. Treat that API as experimental, and verify it in a supported browser if your product depends on it.
:::
