Mocking NGRX Factory Selectors #3279
-
Currently there seems to be no explanation on how to mock factory selectors. Now that selectors with props are going export const selectCustomer = (id: string) => {
createSelector(selectCustomers, (customers) => customers[id]);
} |
Beta Was this translation helpful? Give feedback.
Replies: 24 comments 8 replies
-
Try this: // customer.selectors.ts
export const selectCustomer = (id: string) =>
createSelector(selectCustomers, (customers) => customers[id]);
// spec.ts
import * as customerSelectors from './customer.selectors';
it('should mock factory selector', (done) => {
const customerMock = { id: '1', name: 'c1' };
spyOn(customerSelectors, 'selectCustomer').and.returnValue(() => customerMock);
const store = TestBed.inject(MockStore);
store.select(customerSelectors.selectCustomer('1')).subscribe((customer) => {
expect(customer).toBe(customerMock);
done();
});
}); Note: If you're using Jasmine, set
|
Beta Was this translation helpful? Give feedback.
-
Yeah. This was pretty close, although I am using jest, so I had to modify the code a little. I am testing an angular resolver that has to select stuff from ngrx store. Also the mocked selector factory function output requires a MemoizedSelector output type. so I had to create a fake selector there. I am also using @ngneat/spectator/jest. That's where the spectator variable comes from. it( 'mock ngrx selector factory test', () => {
const fake_job_id = faker.datatype.uuid(); // requires faker
route.params = { job_id: fake_job_id };
const dataMock = [ "1", "2", "3", "4", "5" ];
jest.spyOn( LibsContractorInfoSelectors, 'contractorsLoadedFor' ).mockImplementation(
() => createSelector( () => null, () => dataMock ) // create a fake selector
);
const tst = spectator.service.resolve( route, null ); // requires @ngneat/spectator/jest to set up.
expect( tst ).toBeObservable(
hot('(a|)', { a: dataMock }) // requires jasmine-marbles
)
}); |
Beta Was this translation helpful? Give feedback.
-
Thanks ! This solved most of my issues. Still, when I use multiple times the same selector factory with different arguments in the same effect, I keep getting |
Beta Was this translation helpful? Give feedback.
-
I wonder if you are using NGRX Entities. EntityState has this ids field. Maybe at some point your entity becomes undefined during testing. Because if you mocked your selector - it should just keep returning the same thing unless it gets altered somewhere. |
Beta Was this translation helpful? Give feedback.
-
Is there a different way of mocking the factory selector without changing the |
Beta Was this translation helpful? Give feedback.
-
@Kosmonaft memoize the factory and use return values with |
Beta Was this translation helpful? Give feedback.
-
@dmitryr117 You're right, I am using NgRX Entities. But I already have several tests that work fine with your solution. |
Beta Was this translation helpful? Give feedback.
-
@lhbruneton I guess I you might have to post the code of what you are doing with an effect and the test because it is a bit unclear what is causing it. May have to do something with mocked function returning exactly the same output every time. I was unable to reproduce this issue in my environment. |
Beta Was this translation helpful? Give feedback.
-
Never mind the question, I found the issue. The selector is not used in the component I'm testing but rather in it's entry component. And somehow moving the it('should create', () => {
jest.spyOn(fromSettings, 'selectPreferenceByKey').mockImplementation(() =>
createSelector(
() => null,
() => ({
preferenceKey: Preferences.NmrExportPath,
preferenceType: 'type',
value: 'path',
possibleValues: [],
})
)
);
spectator.detectChanges();
expect(spectator).toBeTruthy();
}); |
Beta Was this translation helpful? Give feedback.
-
@dmitryr117 solution worked for me in my effect specs for anyone else ending up here. I created a factory to create the mocks because I use this all over the place. helper file import { createSelector } from '@ngrx/store';
export const mockNgrxFactorySelectorFactory =
({ jest, moduleImport }) =>
({ method, result }: { method: string; result: any }) => {
jest.spyOn(moduleImport, method as any).mockImplementation(
() =>
createSelector(
() => null,
() => result
) as any
);
}; test file import * as EventSessionSelectors from './event-sessions.selectors';
const mockNgrxEventSessionsSelector = mockNgrxFactorySelectorFactory({
jest,
moduleImport: EventSessionSelectors,
});
mockNgrxEventSessionsSelector({
method: 'getSelectedEntityTrackById',
result: selectedEntityTrack,
}); which replaces provideMockStore({
selectors: [
// not working
{
selector: EventSessionSelectors.getSelectedEntityTrackById(trackId: 'track-id')
value: selectedEntityTrack,,
},
],
}), |
Beta Was this translation helpful? Give feedback.
-
@dmitryr117 Sorry for the late reply. Here is an isolated faulty commit So this is my new factory selector:
Here is my new effect:
And finally here is my new effect test:
And here is the resulting failing test I will try to get a minimal repro running on stackblitz, but it will take some time. |
Beta Was this translation helpful? Give feedback.
-
This solution isn't working with Angular strict mode. Do you have any for strict mode ? |
Beta Was this translation helpful? Give feedback.
-
You can cast fake selector: spyOn(customerSelectors, 'selectCustomer').and.returnValue(
(() => customerMock) as unknown as MemoizedSelector<object, Customer>
); Or create a helper function: function createMemoizedSelectorMock<Result>(
result: Result
): MemoizedSelector<object, Result> {
return createSelector(
() => ({}),
() => result
);
} Usage: spyOn(customerSelectors, 'selectCustomer').and.returnValue(
createMemoizedSelectorMock(customerMock)
); |
Beta Was this translation helpful? Give feedback.
-
Just in case anyone ends up here trying to mock a Factory Selector in a Component that they're testing with Spectator, the suggestions above will work. However, you need to make sure that you still provide an empty Mock Store in your providers: [
provideMockStore()
] Then, before you create your component, setup your spy: import * as yourSelectors from '../your.selectors';
beforeEach(() => {
const dataMock = [ "1", "2", "3", "4", "5" ];
jest.spyOn(yourSelectors, 'selectFactorySelectorName').mockImplementation(
() => createSelector(() => null, () => dataMock) // create a fake selector
);
spectator = createComponent();
}); This should successfully mock anything like this in your component: import * as yourSelectors from '../your.selectors';
factorySelector$ = this.store.select(yourSelectors.selectFactorySelectorName(value)); You can then of course covert this to one of the helper functions suggested above if you are going to be using it a lot. This was probably obvious to most people, but it took me a little while to figure out so I figured I would share. |
Beta Was this translation helpful? Give feedback.
-
Any updates on this? I would really like to know how this can be done as simple as before. |
Beta Was this translation helpful? Give feedback.
-
Export your selectors in an object: export const customerSelectors = {
selectCustomer: (id: string) => {
createSelector(selectCustomers, (customers) => customers[id]);
}
}; Then spying is trivial: import { customerSelectors } from './customer.selectors';
it('should select the customer', (done) => {
const customerMock = { id: '1', name: 'c1' };
spyOn(customerSelectors, 'selectCustomer').and.returnValue(() => customerMock);
const store = TestBed.inject(MockStore);
store.select(customerSelectors.selectCustomer('1')).subscribe((customer) => {
expect(customer).toBe(customerMock);
expect(customerSelectors.selectCustomer).toHaveBeenCalledWith('1');
done();
});
}); |
Beta Was this translation helpful? Give feedback.
-
Moving this to a discussion because this isn't a bug. |
Beta Was this translation helpful? Give feedback.
-
Is there any solution that does not include exporting/importing selectors as an object? How can a factory selector actually be mocked. I tried something like this which seams to not do anything. provideMockStore({
selectors: [
{
selector: selectMemberById('test'),
value: {
name: 'User Name',
},
},
],
}), |
Beta Was this translation helpful? Give feedback.
-
Hi, // customer.selectors.ts
export const selectCustomer = (id: string) =>
createSelector(selectCustomers, (customers) => customers[id]);
// spec.ts
import * as customerSelectors from './customer.selectors';
it('should mock factory selector', (done) => {
const customerMock = { id: '1', name: 'c1' };
spyOnProperty(customerSelectors, 'selectCustomer')
.and.returnValue(() =>
createSelector(
() => ({}),
() => customerMock
)
);
const store = TestBed.inject(MockStore);
store.select(customerSelectors.selectCustomer('1')).subscribe((customer) => {
expect(customer).toBe(customerMock);
done();
});
}); No need to use |
Beta Was this translation helpful? Give feedback.
-
The solutions explained here are not working for me so far in Angular 13. I am spying on the selector in my test as below NOTE: I tried setting the spy in
BUT the error below is still thrown for the production code:
The selector in this case is using a factory (see below) and not props. Any hint of what may be wrong?
|
Beta Was this translation helpful? Give feedback.
-
I came across this thread looking for information on mocking factory selectors; this worked for me, I imagine there was an update to implement this at some intervening point.
|
Beta Was this translation helpful? Give feedback.
-
I have created a spy like below : localPermissionSpy = spyOn(FromAuth.permissionFactorySelector,'hasPermission').and.returnValue((()=> true) as any); before testBed.configureTestModule and try to override the return value later in a test as below : localPermissionSpy.and.returnValue((()=> false) as any); The value returned when i console log it says false but the observable consuming the selector in the component still says the value to be true. can someone suggest a solution for this |
Beta Was this translation helpful? Give feedback.
-
We have found 3 ways of fixing the problem. See the following project for live examples: https://github.com/d-silvas/test-ngrx-factory-selector 1. Mock the parent selectorThis is not ideal because the real selector is still used. // selectors.ts
export const getName1 = (input: string) =>
createSelector(getFeatureState, (state): string => input + ' ' + state.name);
// spec.ts
import * as selectors from './store/selectors';
describe('AppComponent', () => {
let store: MockStore;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent],
providers: [
provideMockStore({
selectors: [{ selector: selectors.getFeatureState, value: { name: 'Mocked1' }}],
}),
],
}).compileComponents();
store = TestBed.inject(MockStore);
const fixture = TestBed.createComponent(AppComponent);
});
describe('selectors', () => {
it('should mock getName1', (done: DoneFn) => {
store.pipe(select(selectors.getName1('Test1'))).subscribe((s) => {
expect(s).toEqual('Test1 Mocked1');
done();
});
});
});
}); 2. Mock the function that creates the selectorThis is the same as the proposed solution. To avoid having to set // selectors.ts
export const factorySelectors = {
getName2: (input: string) =>
createSelector(getFeatureState, (state): string => input + ' ' + state.name)
};
// spec.ts
it('should mock getName2', (done: DoneFn) => {
spyOn(selectors.factorySelectors, 'getName2').and.returnValue(
((_: FeatureState) => 'Mocked2') as any
);
store
.pipe(select(selectors.factorySelectors.getName2('Test2')))
.subscribe((s) => {
expect(s).toEqual('Mocked2');
done();
});
}); 3. Store the selector and override it with the usual ngrx/testing toolsSince calling // selectors.ts
export const getName3 = (input: string) =>
createSelector(getFeatureState, (state): string => input + ' ' + state.name);
// app.component.ts
export class AppComponent {
getName3Selector: any;
constructor() {
this.getName3Selector = selectors.getName3('AppComponent');
}
}
// spec.ts
it('should mock getName3', (done: DoneFn) => {
store.overrideSelector(component.getName3Selector, 'Mocked3');
store.refreshState();
store.pipe(select(component.getName3Selector)).subscribe((s) => {
expect(s).toEqual('Mocked3');
done();
});
}); |
Beta Was this translation helpful? Give feedback.
-
I found most useful to test selectors is when mocking the store. Mock data in store in a way that actual data is in the store. Then when u test your component, just test things in component, and component will use store and all her selectors to get the mocked data. That way u get your selectors tested, and u don't need to explicitly test the selector. |
Beta Was this translation helpful? Give feedback.
Try this:
Note: If you're using Jasmine, set
"module": "commonjs"
intsconfig.spec.json
.