-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add createReducer() with init-state-as-a-lazy-getter overload #1024
Comments
Hmm. A few thoughts:
|
Since the variables declared in the module can't be changed after it was imported, I can't just mock /* ./reducer.js */
import { loadState } from './storage';
export default createReducer(loadState() || '', builder => ...);
/* ./reducer.test.js */
import reducer from './reducer'
jest.mock('./storage', () => ({ loadState: jest.fn() });
// isolated imports with mocked value in storage
const preloadedState = 'some state';
let isolatedReducerWithLoadedState;
jest.isolateModules(() => {
require('./storage').loadState.mockReturnValue(preloadedState);
isolatedReducerWithTruthyInitState = require('./reducer').default;
});
// plain imports assuming there's no value stored
const reducer = require('./reducer').default;
describe('initState', () => {
test('no preloaded state', () => {
expect(reducer(undefined, {})).toBe('');
});
test('preloaded state', () => {
expect(isolatedReducerWithLoadedState(undefined, {}).toBe(preloadedState);
});
}); Partially it is a problem of jest, but they resolved the issue, and I missed it somehow, so the code will become a bit simpler. On the other hand, it will be even cleaner if I don't need to use Maybe this problem has a much better solution, but this is what I use now |
Hmm. My question is, why is it even necessary to test that the reducer returns the initial state? That's basically testing our library code implementation, rather than your app logic. |
In fact, the initial state in my app is not that simple and depends on configuration, etc. So it's more about testing if the code combines data from the storage right. |
Okay. Going back to the actual proposed change: how does passing a function actually help in this case? It's going to be the same function all the time, even if it's being called later. Won't you have the same re-import / mocking issue regardless? |
I see it like this: /* ./reducer.js */
import { loadState, loadAnotherState } from './storage';
function getInitState() {
const state = loadState();
const anotherState = loadAnotherState();
const combined = /* some complex combination */
return combined;
}
export default createReducer(getInitState, builder => ...);
/* ./reducer.test.js */
import { loadState, loadAnotherState } from './storage';
import { reducer } from './reducer';
describe('initState', () => {
test('no preloaded state', () => {
expect(reducer(undefined, {})).toBe('');
});
test('preloaded state', () => {
loadState.mockReturnValue('value');
loadAnotherState.mockReturnValue('another value');
expect(isolatedReducerWithLoadedState(undefined, {}).toBe(properlyCombinedState);
loadState.mockReset();
loadAnotherState.mockReset();
});
}); And that's it. no isolation, no module reinitialization. The initial state will be recomputed every time I call the reducer. Since the combination logic is not exported, I can't test it directly. But I want to check if it works as expected. |
You are jumping a lot of hooks to write a unit test for your combination logic. Why not just add an |
Because it doesn't feel right. Export just for testing smells a lot for me. I'm a kinda test noob and probably don't get how things should be done, so I follow intuition and some aesthetics considerations. |
Testing a function by calling it implicitly through another function doesn't look much better to me 😕 |
Isn't it better? |
So ironically I actually did run into this particular use case myself not too long ago - a slice where we're persisting the data from I think my biggest question would be much more complex this might make the types for |
Type-wise it would not be a problem, but we should be very clear on what it does. Does it execute once when Tbh I was also pondering to expose |
Suggestion from Lenz in chat:
|
In my project, I have a piece of state that may be preloaded from the local storage:
The issue comes when I try to test this reducer because I need to isolate modules (I use Jest, but it may be relevant for other testing libraries, I believe). Without RTK it would be possible to write
and the
loadState()
would be called every time the reducer is called withundefined
as the first argument, but using RTK is much more convenient overall.So is it possible to add
createState<S>(initState: () => S, ...)
overload so that a lazy getter could be passed?The text was updated successfully, but these errors were encountered: