Hone logo
Hone
Problems

Mocking a Redux Store for Unit Testing with Jest and TypeScript

Unit testing components that interact with a Redux store can be tricky. Directly connecting to a real store during testing can lead to slow tests, dependencies on external services, and difficulty isolating component logic. This challenge asks you to create a mock Redux store that allows you to simulate store state and dispatch actions for testing purposes, ensuring your components behave as expected in various scenarios.

Problem Description

You need to create a TypeScript class called MockStore that mimics the behavior of a Redux store for testing purposes. This mock store should allow you to:

  1. Initialize with a state: The mock store should be initialized with an initial state object.
  2. Get the current state: Provide a method to retrieve the current state of the mock store.
  3. Dispatch actions: Implement a dispatch method that accepts an action object and updates the internal state accordingly. The dispatch method should not actually perform any side effects (like API calls) – it should only modify the state.
  4. Subscribe (optional, but recommended): Implement a subscribe method that accepts a listener function. This listener should be called whenever the store's state changes after a dispatch. The listener should receive the new state as an argument. Allow multiple subscriptions.
  5. Unsubscribe: Implement an unsubscribe method that removes a listener from the subscription list.

Expected Behavior:

  • The MockStore should allow you to set an initial state.
  • The dispatch method should update the internal state with the provided action.
  • The getState method should return the current state.
  • The subscribe method should register a listener function.
  • The unsubscribe method should remove a registered listener.
  • Subscribed listeners should be called with the new state after each dispatch.

Edge Cases to Consider:

  • Dispatching actions with no effect on the state.
  • Multiple subscriptions to the store.
  • Unsubscribing listeners.
  • Calling getState before any actions have been dispatched.

Examples

Example 1:

Input:
const mockStore = new MockStore({ count: 0 });
mockStore.dispatch({ type: 'INCREMENT' });
mockStore.dispatch({ type: 'DECREMENT' });

Output:
mockStore.getState() === { count: 0 }

Explanation: The initial state is { count: 0 }. Dispatching 'INCREMENT' and 'DECREMENT' doesn't change the state because there's no reducer logic in the mock store.

Example 2:

Input:
const mockStore = new MockStore({ user: null });
const listener = (state) => console.log('State changed:', state);
mockStore.subscribe(listener);
mockStore.dispatch({ type: 'SET_USER', payload: { id: 1, name: 'Alice' } });
mockStore.unsubscribe(listener);
mockStore.dispatch({ type: 'CLEAR_USER' });

Output:
Console output: "State changed: { user: { id: 1, name: 'Alice' } }"
(No further console output after unsubscribe)

Explanation: The listener is called with the updated state after the 'SET_USER' action. After unsubscribing, subsequent dispatches don't trigger the listener.

Example 3: (Multiple Subscriptions)

Input:
const mockStore = new MockStore({ data: [] });
const listener1 = (state) => console.log('Listener 1:', state);
const listener2 = (state) => console.log('Listener 2:', state);
mockStore.subscribe(listener1);
mockStore.subscribe(listener2);
mockStore.dispatch({ type: 'ADD_DATA', payload: [1, 2, 3] });
mockStore.unsubscribe(listener1);
mockStore.dispatch({ type: 'REMOVE_DATA', payload: [1] });

Output:
Console output:
"Listener 1: { data: [ 1, 2, 3 ] }"
"Listener 2: { data: [ 1, 2, 3 ] }"
"Listener 2: { data: [ 2, 3 ] }"

Explanation: Both listeners are called after the first dispatch. After listener1 is unsubscribed, only listener2 is called on subsequent dispatches.

Constraints

  • The MockStore class must be written in TypeScript.
  • The dispatch method should not perform any actual side effects.
  • The getState method should return a copy of the internal state (to prevent external modification).
  • The subscribe method should accept a function that takes the store's state as an argument.
  • The unsubscribe method should remove the provided listener function.
  • The initial state should be an object.

Notes

  • Consider using a simple array to manage subscriptions.
  • Focus on mimicking the core functionality of a Redux store for testing purposes.
  • You don't need to implement complex Redux features like middleware or enhancers.
  • Think about how to ensure that the getState method returns a copy of the state to prevent accidental modification of the internal state from outside the MockStore. This is important for test isolation.
Loading editor...
typescript