diff --git a/packages/ra-test/README.md b/packages/ra-test/README.md
index 4d2dee866f2..67d0629d960 100644
--- a/packages/ra-test/README.md
+++ b/packages/ra-test/README.md
@@ -65,6 +65,20 @@ testUtils = render(
This means that reducers will work as they will within the app.
+### Passing your custom reducers
+
+If your component relies on customReducers which are passed originally to the `` component, you can plug them in the TestContext using the `customReducers` props:
+
+```jsx
+testUtils = render(
+
+
+
+);
+```
+
+Note you should also enable the default react-admin reducers in order to supply the custom ones.
+
### Spying on the store 'dispatch'
If you are using `useDispatch` within your components, it is likely you will want to test that actions have been dispatched with the correct arguments. You can return the `store` being used within the tests using a `renderProp`.
@@ -86,6 +100,36 @@ it('should send the user to another url', () => {
});
```
+### Using the 'renderWithRedux' wrapper function
+
+Instead of wrapping the component under test with the `TestContext` by yourself you can use all of the above options and test your components almost like using just `@testing-library/react` thanks to the `renderWithRedux` wrapper function.
+
+It will return the same output as the `render` method from `@testing-library/react` but will add the `dispatch` and `reduxStore` helpers.
+
+```jsx
+import { defaultStore } from 'ra-test'
+...
+const { dispatch, reduxStore, ...testUtils } = renderWithRedux(
+ ,
+ initialState,
+ options,
+ myCustomReducers
+);
+
+it('should initilize store', () => {
+ const storeState = reduxStore.getState();
+ storeState.router.location.key = ''
+ expect(storeState).toEqual({...defaultStore, ...initialState});
+});
+
+it('send the user to another url', () => {
+ fireEvent.click(testUtils.getByText('Go to next'));
+ expect(dispatch).toHaveBeenCalledWith(`/next-url`);
+});
+```
+
+All of the arguments except the first one - the component under test, are optional and could be omitted by passing an empty object - `{}`
+
### Testing Permissions
As explained on the [Auth Provider chapter](./Authentication.md#authorization), it's possible to manage permissions via the `authProvider` in order to filter page and fields the users can see.
diff --git a/packages/ra-test/src/TestContext.spec.tsx b/packages/ra-test/src/TestContext.spec.tsx
index 40fbea7b763..15ea5332396 100644
--- a/packages/ra-test/src/TestContext.spec.tsx
+++ b/packages/ra-test/src/TestContext.spec.tsx
@@ -32,6 +32,35 @@ const primedStore = {
},
};
+const CHANGE_FOO = 'CHANGE_FOO';
+
+const customReducerInitialState = {
+ foo: 'bar',
+ foo2: 'bar2',
+};
+
+const customAction = payload => ({
+ type: CHANGE_FOO,
+ payload,
+});
+
+const customReducer = (prevState = customReducerInitialState, action) => {
+ switch (action.type) {
+ case CHANGE_FOO:
+ return {
+ ...prevState,
+ foo: action.payload,
+ };
+ default:
+ return prevState;
+ }
+};
+
+const eraseRouterKey = state => {
+ state.router.location.key = ''; // react-router initializes the state with a random key
+ return state;
+};
+
describe('TestContext.js', () => {
it('should render the given children', () => {
const { queryAllByText } = render(
@@ -69,8 +98,7 @@ describe('TestContext.js', () => {
}}
);
- const initialstate = testStore.getState();
- initialstate.router.location.key = ''; // react-router initializes the state with a random key
+ const initialstate = eraseRouterKey(testStore.getState());
expect(initialstate).toEqual(primedStore);
testStore.dispatch(refreshView());
@@ -103,5 +131,53 @@ describe('TestContext.js', () => {
expect(testStore.getState()).toEqual(defaultStore);
});
+
+ it('should initilize the state with customReducers initialState', () => {
+ let testStore;
+ render(
+
+ {({ store }) => {
+ testStore = store;
+ return foo;
+ }}
+
+ );
+ const initialstate = eraseRouterKey(testStore.getState());
+
+ expect(initialstate).toEqual({
+ ...primedStore,
+ customReducer: customReducerInitialState,
+ });
+ });
+
+ it('should update the state on customReducers action', () => {
+ const testValue = 'test';
+ let testStore;
+ render(
+
+ {({ store }) => {
+ testStore = store;
+ return foo;
+ }}
+
+ );
+
+ testStore.dispatch(customAction(testValue));
+ const alteredState = eraseRouterKey(testStore.getState());
+
+ expect(alteredState).toEqual({
+ ...primedStore,
+ customReducer: {
+ ...customReducerInitialState,
+ foo: testValue,
+ },
+ });
+ });
});
});
diff --git a/packages/ra-test/src/TestContext.tsx b/packages/ra-test/src/TestContext.tsx
index edd5e2e11ef..015fbe18964 100644
--- a/packages/ra-test/src/TestContext.tsx
+++ b/packages/ra-test/src/TestContext.tsx
@@ -33,6 +33,7 @@ export interface TestContextProps {
initialState?: object;
enableReducers?: boolean;
history?: History;
+ customReducers?: object;
children: ReactNode | TextContextChildrenFunction;
}
@@ -69,7 +70,11 @@ export class TestContext extends Component {
constructor(props) {
super(props);
this.history = props.history || createMemoryHistory();
- const { initialState = {}, enableReducers = false } = props;
+ const {
+ initialState = {},
+ enableReducers = false,
+ customReducers = {},
+ } = props;
this.storeWithDefault = enableReducers
? createAdminStore({
@@ -78,6 +83,7 @@ export class TestContext extends Component {
Promise.resolve(dataProviderDefaultResponse)
),
history: createMemoryHistory(),
+ customReducers,
})
: createStore(() => merge({}, defaultStore, initialState));
}
diff --git a/packages/ra-test/src/renderWithRedux.tsx b/packages/ra-test/src/renderWithRedux.tsx
index 40abe3c55e8..f2a7523cadd 100644
--- a/packages/ra-test/src/renderWithRedux.tsx
+++ b/packages/ra-test/src/renderWithRedux.tsx
@@ -16,9 +16,19 @@ export interface RenderWithReduxResult extends RenderResult {
* initialState
* );
*
+ * render with react-testing library adding redux context for unit test and passing customReducers.
+ * @example
+ * const { dispatch, reduxStore, ...otherReactTestingLibraryHelper } = renderWithRedux(
+ * ,
+ * initialState,
+ * {},
+ * customReducers
+ * );
+ *
* @param {ReactNode} component: The component you want to test in jsx
* @param {Object} initialState: Optional initial state of the redux store
* @param {Object} options: Render options, e.g. to use a custom container element
+ * @param {Object} customReducers: Custom reducers to be added to the default store
* @return {{ dispatch, reduxStore, ...rest }} helper function to test rendered component.
* Same as @testing-library/react render method with added dispatch and reduxStore helper
* dispatch: spy on the redux store dispatch method
@@ -27,12 +37,17 @@ export interface RenderWithReduxResult extends RenderResult {
export const renderWithRedux = (
component,
initialState = {},
- options = {}
+ options = {},
+ customReducers = {}
): RenderWithReduxResult => {
let dispatch;
let reduxStore;
const renderResult = render(
-
+
{({ store }) => {
dispatch = jest.spyOn(store, 'dispatch');
reduxStore = store;
@@ -46,7 +61,11 @@ export const renderWithRedux = (
...renderResult,
rerender: newComponent => {
return renderResult.rerender(
-
+
{({ store }) => {
dispatch = jest.spyOn(store, 'dispatch');
reduxStore = store;