React mock
Declarative mocks for React state and global APIs.
Jump to
Why?
The motivation for this project comes from wanting to load any type of React component in isolation—inside automated tests as well as in component explorers such as Cosmos or Storybook. Some components as stateful, while others fetch data or interact with some other external input.
The aim here is to isolate all components, not just presentational and stateless components.
Declarative
Tools like fetch-mock and xhr-mock are already used by many of us in component tests. But they require imperative setups and teardowns, which has two drawbacks:
-
They require before/after orchestration in component tests, which is tedious and can create convoluted test cases.
-
They're difficult to integrate in component explorers where a usage file is a declarative component element.
To overcome these drawbacks, react-mock
offers mocking techniques as declarative React elements. Lifecycle methods take care of setting up and reverting mocks behind the hood.
Composition
Two or more mocks can be composed into a single React element.
render(
<LocalStorageMock items={{ userId: 123 }}>
<FetchMock matcher="/user/123" response={{ name: 'Jessica' }}>
<StateMock state={{ show: true }}>
<ToggleShow>
<UserGreeting />
</ToggleShow>
</StateMock>
</FetchMock>
</LocalStorageMock>
);
Limitations
-
Some react-mock components mock a global API entirely, like fetch or localStorage. For this reason only one instance of each should be rendered at once. There might be ways to compose these mocks in the future.
-
To keep this codebase light, the declarative APIs mirror the params of their underlying APIs. Eg. Although they both mock server requests, the
FetchMock
API is different from theXhrMock
API because they rely on different libs. More concise interfaces are possible, but they increase the scope of this project.
Component state
Inject React component state declaratively.
StateMock
must be the direct parent of the stateful component for the state injection to work.
import { StateMock } from '@react-mock/state';
render(
<StateMock state={{ count: 5 }}>
<Counter />
</StateMock>
);
Warning: StateMock delays ref calls. This means refs can get called after componentDidMount, instead of before as you might expect.
Fetch requests
A declarative wrapper for the wonderful fetch-mock.
Note: FetchMock mocks the global Fetch API, so only one FetchMock instance should be rendered at once.
import { FetchMock } from '@react-mock/fetch';
// Passing fetch-mock options
render(
<FetchMock options={{ matcher: '/login', response: 401, method: 'POST' }}>
<MyComponent />
</FetchMock>
);
// Passing fetch-mock config
render(
<FetchMock
matcher="/posts"
response={200}
config={{ fallbackToNetwork: true }}
>
<MyComponent />
</FetchMock>
);
Multiple mocks
render(
<FetchMock
mocks={[
{ matcher: '/users', response: [{ id: 123 }] },
{ matcher: '/user/123', response: { name: 'Jessica' } }
]}
>
<MyComponent />
</FetchMock>
);
Inspection
See fetch-mock's inspection methods to check how fetch was called.
Note: Import
fetchMock
from @react-mock/fetch to ensure you're inspecting on the right fetch-mock instance.
import { fetchMock } from '@react-mock/fetch';
const [, { body }] = fetchMock.lastCall('/login', 'POST');
expect(JSON.parse(body)).toEqual({ user: 'harry' });
LocalStorage
Mock LocalStorage data declaratively.
Note: LocalStorageMock mocks the global localStorage API, so only one LocalStorageMock instance should be rendered at once.
import { LocalStorageMock } from '@react-mock/localstorage';
render(
<LocalStorageMock items={{ sessionId: 're4lt0k3n' }}>
<MyComponent />
</LocalStorageMock>
);
XHR requests
A declarative wrapper for the great xhr-mock.
Note: XhrMock mocks the global XMLHttpRequest API, so only one XhrMock instance should be rendered at once.
import { XhrMock } from '@react-mock/xhr';
// GET
render(
<XhrMock
url="/users"
response={(req, res) => res.body(JSON.stringify(users))}
>
<MyComponent />
</XhrMock>
);
// POST
render(
<XhrMock url="/login" method="POST" response={(req, res) => res.status(401)}>
<MyComponent />
</XhrMock>
);
Multiple mocks
const res = body => (req, res) => res.body(JSON.stringify(body));
render(
<XhrMock
mocks={[
{ url: '/users', response: res([{ id: 123 }]) },
{ url: '/user/123', response: res({ name: 'Jessica' }) }
]}
>
<MyComponent />
</XhrMock>
);
How to contribute
Intention
Please take a minute to understand this project's purpose and ensure your contribution is thoughtful and relevant. Preserving the integrity of an open source project is hard. Thanks!
Check your code
You have the following weapons at your disposal: yarn lint
, yarn flow
and yarn test
. Use them.
New package
Run yarn new-package
and you'll follow this friendly flow that will generate initial boilerplate.
Docs
Each package has its own README. This is useful for keeping docs close to code, as well as for showing docs on each package's npm page.
The root README is generated using a script. Do not edit it by hand. It's assembled from a template, individual package docs and the CONTRIBUTING.md.
Run npm generate-readme
to update the root README.
License
MIT © Ovidiu Cherecheș