Jest mocking utilities for working with the DOM.
$ yarn add @shopify/jest-dom-mocks
This package provides two methods that should be included in the jest setup files:
ensureMocksReset
installMockStorage
Should be called in the beforeEach
method of the jest each-test
setup file. For example:
import {ensureMocksReset} from '@shopify/jest-dom-mocks';
beforeEach(() => {
ensureMocksReset();
});
this will ensure that appropriate error messages are shown if a DOM object is mocked without beign restored for the next test.
Should be called in the jest setup
file. For example:
import {installMockStorage} from '@shopify/jest-dom-mocks';
installMockStorage();
this will install the localStorage
and sessionStorage
mocks onto the global window
object.
In this example, we are testing a NumberTransitioner
component using Jest
and Enzyme
. Note that parts of this file have been omitted in order to focus in on the relevant parts of the example.
import {clock, animationFrame} from '@shopify/jest-dom-mocks';
it('transitions to the next number after being updated', () => {
clock.mock();
animationFrame.mock();
const duration = 1000;
const rendered = mount(
<NumberTransitioner duration={duration}>{100}</NumberTransitioner>,
);
rendered.setProps({children: 200});
clock.tick(duration / 4);
animationFrame.runFrame();
expect(rendered.text()).toBe('125');
clock.tick(duration / 2);
animationFrame.runFrame();
expect(rendered.text()).toBe('175');
clock.restore();
animationFrame.restore();
});
The mocks provided can be divided into 3 primary categories:
- standard mocks
- fetch mock
- storage mocks
The following standard mocks are available:
animationFrame
requestIdleCallback
clock
location
matchMedia
timer
promise
intersectionObserver
dimension
connection
Each of the standard mocks can be installed, for a given test, using standardMock.mock()
, and must be restored before the end of the test using standardMock.restore()
.
For example:
import {location} from '@shopify/jest-dom-mocks';
beforeEach(() => {
location.mock();
});
afterEach(() => {
location.restore();
});
it('does a thing', () => {
// run test code here
});
Or, if you just need to mock something for a single test:
import {location} from '@shopify/jest-dom-mocks';
it('does a thing', () => {
location.mock();
// run test code here
location.restore();
});
Some of the standard mocks include additional features:
Executes all queued animation callbacks.
Removes window.requestIdleCallback
and window.cancelIdleCallback
, which can be useful for testing features that should work with and without idle callbacks available.
Runs all currently-scheduled idle callbacks. If provided, timeRemaining
/ didTimeout
will be used to construct the argument for these callbacks. Once called, all callbacks are removed from the queue.
Cancels all currently-scheduled idle callbacks.
Cancels the idle callback specified by the passed argument. This value should be the one returned from a call to window.requestIdleCallback
.
In addition to the usual .mock()
functionality (with no arguments), the Clock
object can be mock
ed by passing in a number
or Date
object to use as the current system time.
Ticks the mocked Clock
ahead by time
milliseconds.
Sets the system time to the given time
.
In addition to the usual .mock()
functionality (with no arguments), the MatchMedia
object can be mock
ed by passing in a MediaMatching
function to use as the implementation.
The MediaMatching
function has the following interface:
interface MediaMatching {
(mediaQuery: string): Partial<MediaQueryList>;
}
it takes a mediaQuery
string as input and returns a partial MediaQueryList
to use as the result of window.matchMedia(mediaQuery)
. The partial result will be merged with the default values:
{
media: '',
addListener: noop,
removeListener: noop,
matches: false
}
Sets the implementation function for the mocked MatchMedia
object. see above (MatchMedia.mock(media?: MediaMatching): void
) for details on how MediaMatching
works.
You can also call setMedia
with no arguments to restore the default implementation.
Runs all system timers to completion.
Runs all system timers to the given time
.
Runs all promise resolvers that have been queued.
Returns an array of records representing elements currently being observed with an IntersectionObserver
. Each record contains a target
(the element being observed), callback
(the function used when constructing the observer), options
(optional object used when constructing the observer), and a source
(the fake IntersectionObserver
instance that was used to observe).
IntersectionObserver.simulate(entry: Partial<IntersectionObserverEntry> | Partial<IntersectionObserverEntry>[]): void
Simulates a call on all matching observers. If you pass a target
on the passed entry/ entries, only observers with a matching target
element will be triggered. Otherwise, all observers will be triggered. If you do not provide a full IntersectionObserverEntry
in any case, the missing fields will be filled out with sensible defaults.
We use a version of fetch-mock
that is augmented to ensure that it is properly unmocked after each test run. See the API of fetch-mock
for more details.
The storage mocks are a bit different than the other mocks, because they serve primarily as a polyfill for the localStorage
and sessionStorage
APIs. The following standard API methods are implemented:
getItem
setItem
removeItem
clear
Each of these are wrapped in a jest spy, which is automatically restored at the end of the test run.
The dimension mocks allow mocking the following DOM properties:
scrollWidth
scrollHeight
offsetWidth
offsetHeight
innerWidth
Pass the dimension you want to mock and the value you want returned for all calls when calling mock
:
import {dimension} from '@shopify/jest-dom-mocks';
beforeEach(() => {
dimension.mock({
scrollWidth: 100,
offsetHeight: 200,
});
});
afterEach(() => dimension.restore());
You can also pass in a function as a mock that returns a number. The element is passed as the only argument to the function:
beforeEach(() => {
dimension.mock({
scrollWidth(element: HTMLElement) {
return element.id === 'test-id' ? 200 : 0;
},
});
});
afterEach(() => dimension.restore());
describe('DOM tests', () => {
it('returns the element width', () => {
function Component() {
return <div id="some-id" />;
}
const element = mount(<Component />);
const elementWidth =
element.domNode == null ? undefined : element.domNode.scrollWidth;
expect(elementWidth).toBe(200);
});
});