Mocking framer-motion v4
Tags: react, testing, javascript, css
Published: 2021-04-14 23:44:29

[Testing Library](https://testing-library.com/) has changed the UI testing game for the better. If you haven't tried it yet, check it out.

At [work](https://www.process.st/) our new UI efforts are powered by [Chakra UI](https://chakra-ui.com/), which uses [Framer Motion](https://www.framer.com/motion/) under the hood for animations. With all this work, we're testing all of it using [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) (RTL).

One great way to defend against UI regressions (copy, styles, etc) is [snapshot testing](https://jestjs.io/docs/snapshot-testing). As we're getting more and more into Chakra's features and better tests, we've run into issues where the animated `style` properties have minute differences between snapshots.

RTL [recommends](https://testing-library.com/docs/react-testing-library/faq/) mocking animation libraries to solve this problem. There are a few solutions for doing this with `framer-motion` on the web, but I don't think they are up to snuff for the current version of the library (`4._`).

After digging around the [framer motion source](https://github.com/framer/motion/blob/main/src/render/dom/motion.ts), I realized our attempt at mocking the `motion` export as an object (see [here](https://www.hectane.com/blog/mock-framer-motion-with-jest)) wasn't working because `motion` is constructed using [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).

## Enough with the words, how do I stabilize my snapshot tests?!

```tsx
// __mocks__/framer-motion.ts

import { CustomDomComponent, CustomMotionComponentConfig } from 'framer-motion/types/render/dom/motion-proxy';
import * as React from 'react';

const actual = jest.requireActual('framer-motion');

// https://github.com/framer/motion/blob/main/src/render/dom/motion.ts
function custom<Props>(
  Component: string | React.ComponentType<Props>,
  _customMotionComponentConfig: CustomMotionComponentConfig = {},
): CustomDomComponent<Props> {
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  return React.forwardRef((props, ref) => {
    const regularProps = Object.fromEntries(
      // do not pass framer props to DOM element
      Object.entries(props).filter(([key]) => !actual.isValidMotionProp(key)),
    );
    return typeof Component === 'string' ? (
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      <div ref={ref} {...regularProps} />
    ) : (
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      <Component ref={ref} {...regularProps} />
    );
  });
}

const componentCache = new Map<string, unknown>();
const motion = new Proxy(custom, {
  get: (_target, key: string) => {
    if (!componentCache.has(key)) {
      componentCache.set(key, custom(key));
    }

    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    return componentCache.get(key)!;
  },
});

module.exports = {
  __esModule: true,
  ...actual,
  AnimatePresence: ({ children }: { children: React.ReactChildren }) => <>{children}</>,
  motion,
};
```

Now in your test setup file you can call `jest.mock('framer-motion')` and all the animation related properties will be filtered out.

Happy testing!