Skip to content

Instantly share code, notes, and snippets.

@seanmhanson
Created July 20, 2022 15:23
Show Gist options
  • Save seanmhanson/2a236284bef925f679a73ca96eddde49 to your computer and use it in GitHub Desktop.
Save seanmhanson/2a236284bef925f679a73ca96eddde49 to your computer and use it in GitHub Desktop.
RTK Query Testing Utils - Setup API Store
import {
AnyAction,
combineReducers,
configureStore,
EnhancedStore,
Middleware,
Reducer,
} from '@reduxjs/toolkit';
import { CurriedGetDefaultMiddleware as GetDefaultMiddleware } from '@reduxjs/toolkit/dist/getDefaultMiddleware';
/* eslint-disable @typescript-eslint/no-explicit-any */
interface DefaultApi {
reducer: Reducer<any, any>;
reducerPath: string;
middleware: Middleware;
util: { resetApiState(): any };
}
interface DefaultReducers {
[key: string]: Reducer<any, any>;
}
interface ApiStore {
api: any;
store: EnhancedStore;
}
/*
* A utility for creating mock stores used in testing the actions/behaviors of our API, independent
* of the UI.
*
* - this is similar to "redux-mock-store" when testing with redux, but this also includes the API
* reducers, whereas redux-mock-store does not us to include reducers
*
* - Based upon RTK Query's helper function, as per John McDowell
* RTK Query fn: https://github.com/reduxjs/redux-toolkit/blob/master/packages/toolkit/src/query/tests/helpers.tsx
* McDowell example: https://medium.com/@johnmcdowell0801/testing-rtk-query-with-jest-cdfa5aaf3dc1
*/
export function setupStoreWithApi<
Api extends DefaultApi,
ExtraReducers extends DefaultReducers = Record<never, never>,
>(api: Api, extraReducers?: ExtraReducers): ApiStore {
const getStore = (): EnhancedStore => {
const reducers = {
[api.reducerPath]: api.reducer,
...extraReducers,
};
const middleware = (getDefaultMiddleware: GetDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
immutableChecK: false,
}).concat(api.middleware);
return configureStore({ reducer: combineReducers(reducers), middleware });
};
/**
* Rewritten for clarity from the RTK function, this creates the type that matches the
* created store, but as an explicit type (without the generics seen above as "Api" and
* "ExtraReducers"), so that the returned store will already be correctly typed
*/
type ReducersFromApi = { api: ReturnType<Api['reducer']> };
type ReducersFromOptions = {
[K in keyof ExtraReducers]: ReturnType<ExtraReducers[K]>;
};
type StoreReducers = ReducersFromApi & ReducersFromOptions;
// middleware expected from the store, if any; otherwise, this is omitted entirely
type ExpectedStoreType = ReturnType<typeof getStore>;
type StoreMiddleware = ExpectedStoreType extends EnhancedStore<
any,
any,
infer M
>
? M
: never;
type StoreType = EnhancedStore<StoreReducers, AnyAction, StoreMiddleware>;
// initialize the api and store, ensuring that store is correctly cast after initialization
const initialStore = getStore() as StoreType;
const ref = { api, store: initialStore };
const store = getStore() as StoreType;
ref.store = store;
return ref;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment