An NgRx MockStore test calls setResult on an overridden cart-total selector, but the view keeps the old total; what is missing, and why reset selectors afterwards?
answer
- a new value is not an emission
- selectors re-run on state emissions
- re-emit the same state
- overrides live on the selector object
- afterEach cleanup
basics
~10 ssetResult changes what the selector returns but emits nothing; call store.refreshState() so subscribers re-evaluate. Overrides live on the shared selector objects, so call store.resetSelectors() in afterEach or they leak into later tests.
solid answer
~40 s`overrideSelector` returns the memoized selector, and `setResult(30)` changes what it returns, but no one re-runs it: `store.select` re-evaluates only when the state emits, and `selectSignal`'s computed only when the state signal changes. `store.refreshState()` re-emits a shallow copy of the last state, so every selector re-runs and the view gets 30 after change detection. `setState(next)` also emits, but it **replaces** the whole mock state, which matters for selectors you did not override. The second half: an override is stored on the selector function itself, a module-level singleton, not on the `MockStore`. A fresh `TestBed` and a fresh mock store do not clear it, so a later test in the file that does not pin that selector still gets 30. `store.resetSelectors()` in `afterEach` calls `release()` and `clearResult()` on every selector the store overrode.
code
ts · 30 linesimport { TestBed } from '@angular/core/testing';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { CartSummaryComponent } from './cart-summary.component';
import { selectCartTotal } from './cart.selectors';
describe('CartSummaryComponent totals', () => {
let store: MockStore;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [CartSummaryComponent],
providers: [provideMockStore()],
});
store = TestBed.inject(MockStore);
});
afterEach(() => store.resetSelectors());
it('shows a new total after the pinned value changes', () => {
const total = store.overrideSelector(selectCartTotal, 45);
const fixture = TestBed.createComponent(CartSummaryComponent);
fixture.detectChanges();
total.setResult(30);
store.refreshState();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('30');
});
});go deeper
Recall that changing a pinned selector value in an NgRx mock store needs refreshState before the view shows it.
Explain that select and selectSignal re-run selectors only on state emissions, and how setState and refreshState each cause one.
Diagnose order-dependent suites: overrides live on shared selector objects, so resetSelectors belongs in afterEach; know setState replaces rather than merges.
Bake the hygiene into a shared test setup so no team re-learns the selector leak through flaky, order-dependent suites.
## The pieces involved `MockStore` (from `@ngrx/store/testing`) is a test double for NgRx's `Store`. It holds a mock state in a `BehaviorSubject`, runs no reducers, and lets tests pin **memoized selectors** to fixed values. Five members decide how a pinned value reaches a view: | Member | Does | Emits to subscribers? | |---|---|---| | `overrideSelector(sel, value)` | pins `sel` and returns it | no | | `sel.setResult(value)` | changes the pinned value | no | | `setState(next)` | replaces the whole mock state | yes | | `refreshState()` | re-emits a shallow copy of the last state | yes | | `resetSelectors()` | releases and un-pins every override | no | ## Why setResult alone does nothing visible A component reads the store in one of two ways: - `store.select(selectCartTotal)` — an observable that maps each state emission through the selector and drops repeats with `distinctUntilChanged`; - `store.selectSignal(selectCartTotal)` — a `computed` over the store's state signal. Both re-evaluate the selector **only when the state changes**. `setResult(30)` changes what the selector would return if called, but the state has not emitted, so nothing calls it. The view keeps showing 45. The same is true of calling `overrideSelector` a second time with a new value: it calls `setResult` internally and emits nothing. ## refreshState versus setState `refreshState()` takes the last state and emits `{ ...lastState }`. The new reference triggers both the observable pipeline and the state signal, every selector re-runs, the overridden one returns 30, and after change detection the view shows it. No data changes. `setState(next)` also emits, but it **replaces** the state: there is no merge. Use it when the test is about raw state, for selectors you have not pinned. A pinned selector ignores the state entirely, so `setState` with a different cart does not change its output either. The order that works: 1. Pin or change pinned values with `overrideSelector` or `setResult`. 2. Push a state emission with `refreshState()` (or `setState` if raw state also changes). 3. Run change detection and assert. ## Why overrides leak between tests This is the part that bites real suites. `overrideSelector` does not record the value in the `MockStore` alone; it calls `setResult` on the selector object, and the selector object is a **module-level singleton** imported by every test in the file. When the next test gets a new `TestBed` and a new `MockStore`, the selector still carries the override. The new store's constructor only resets the overrides it knows about, and it knows none. Symptoms: - a test that uses real state for `selectCartTotal` sees 30 from an earlier test; - a whole-state selector test in the same file returns the pinned value instead of computing; - tests pass alone and fail when run together, or depend on order. `resetSelectors()` iterates the selectors this store overrode, calls `release()` (clears memoization) and `clearResult()` (removes the pin), and forgets them. Putting `store.resetSelectors()` in `afterEach` is what the NgRx testing guide shows. ## A checklist for mock-store suites - Pin selectors in `beforeEach` or the `selectors` config, not scattered through tests. - After changing a pinned value, call `refreshState()` before asserting. - Use `setState` for raw state, remembering it replaces rather than merges. - Call `resetSelectors()` in `afterEach`, always. ## Reading a failing suite When a mock-store suite behaves strangely, the symptom usually points at one of the members above: - **The view never updates after a new pinned value** — a missing `refreshState()` after `setResult` or a second `overrideSelector`. - **A selector returns a pinned value in a test that never pinned it** — a missing `resetSelectors()` in an earlier test's `afterEach`. - **A non-overridden selector suddenly returns `undefined` or throws** — a `setState` call that replaced the whole state with a partial object. - **Memoization seems stuck between tests** — `release()` was never called; `resetSelectors()` calls it for overridden selectors, and a whole-state selector test can call `release()` itself. These are senior-level questions because they only appear in real suites of some size: a single test file with one case never shows the leak, and the failure moves around as test order changes.
- Why does the leak survive TestBed's reset between tests in NgRx?TestBed discards injectors and instances, but the override is stored on the selector function, which is a module export shared by every test in the file. A new `MockStore` only resets overrides it applied itself, so an earlier store's pin stays until someone calls `resetSelectors()` on that store or `clearResult()` on the selector.
- When would you use setState instead of refreshState in an NgRx MockStore test?When the raw state must change for selectors you have not pinned, for example to show the component's empty-cart branch through a real selector. `setState` replaces the entire mock state, so pass a complete object. `refreshState` changes no data; it only re-emits so pinned values reach subscribers.
saying these in an interview costs you the question
- setResult makes the overridden selector emit its new value immediately.
- setState merges the partial object into the current mock state.
- TestBed resets between tests, so selector overrides can never leak.
- refreshState restores every overridden selector to its real projection.
- resetSelectors resets the mock state back to its initialState.