I have a component that fetches some information from a NGXS Store, that I need to create unit tests for. The component looks like this:
@Component({
standalone: true,
selector: 'app-task-selection-menu',
templateUrl: './task-selection-menu.component.html',
styleUrl: './task-selection-menu.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TextComponent, CheckboxComponent, TooltipModule],
})
export class TaskSelectionMenuComponent implements OnInit, OnDestroy {
private readonly store = inject(Store);
private readonly configManager = inject(ConfigurationManagerService);
private readonly cdr = inject(ChangeDetectorRef);
private readonly destroy$ = new Subject<void>();
readonly Icons = Icons;
id: string | undefined;
tasks = select(TaskSelectionSelectors.props.tasks);
state = select(TaskSelectionSelectors.props.state);
ngOnInit(): void {
this.getDailyDetails();
}
ngOnDestroy(): void {
this.destroy$.next();
}
async getDailyDetails() {
const dailyTasks = await this.configManager.getTasks('daily');
if (dailyTasks) {
this.id = dailyTasks.id;
this.store.dispatch(new TaskSelectionActions.FetchAll(this.id));
}
}
isTaskSelected(task: Task): boolean {
return task.selectionState === TaskSelectionState.Selected;
}
updateTask(task: Task, $event: boolean) {
if (this.id) {
const newState = {
selectionState: $event ? TaskSelectionState.Selected : TaskSelectionState.Unselected,
};
this.store.dispatch(
new TaskSelectionActions.UpdateTask(this.id, task.id, newState),
);
}
this.cdr.markForCheck();
}
// some methods for displaying the list of checkboxes in the template correctly
}
I've tried a few different setups for the testing, but I keep getting Errors like this:
// if i mock out select/selectSignal/dispatch and create the Store as a Jasmine SpyObj
FAILED: should create
NullInjectorError: R3InjectorError(Standalone[TaskSelectionMenuComponent])[Logger -> Logger -> LogTarget -> LogTarget]:
NullInjectorError: No provider for LogTarget!
error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'Logger', 'Logger', 'LogTarget', 'LogTarget' ] })
at NullInjector.get (node_modules/@angular/core/fesm2022/core.mjs:1630:27)
at R3Injector.call (node_modules/@angular/core/fesm2022/core.mjs:2159:33)
at R3Injector.apply (node_modules/ng-mocks/index.mjs:1:129465)
at R3Injector.get (node_modules/ng-mocks/index.mjs:1:6416)
at R3Injector.call (node_modules/@angular/core/fesm2022/core.mjs:2159:33)
at R3Injector.apply (node_modules/ng-mocks/index.mjs:1:129465)
at R3Injector.get (node_modules/ng-mocks/index.mjs:1:6416)
at injectInjectorOnly (node_modules/@angular/core/fesm2022/core.mjs:1125:32)
at ɵɵinject (node_modules/@angular/core/fesm2022/core.mjs:1131:60)
at inject (node_modules/@angular/core/fesm2022/core.mjs:1217:12)
// if i don't mock out select/selectSignal/dispatch and inject the Store into the storeSpy, I get this error:
FAILED: should create
NullInjectorError: R3InjectorError(DynamicTestModule)[Store -> InternalStateOperations -> InternalDispatcher -> InternalNgxsExecutionStrategy -> InjectionToken NGXS_EXECUTION_STRATEGY -> InjectionToken CUSTOM_NGXS_EXECUTION_STRATEGY -> InjectionToken CUSTOM_NGXS_EXECUTION_STRATEGY]:
NullInjectorError: No provider for InjectionToken CUSTOM_NGXS_EXECUTION_STRATEGY!
error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'Store', 'InternalStateOperations', 'InternalDispatcher', 'InternalNgxsExecutionStrategy', 'InjectionToken NGXS_EXECUTION_STRATEGY', 'InjectionToken CUSTOM_NGXS_EXECUTION_STRATEGY', 'InjectionToken CUSTOM_NGXS_EXECUTION_STRATEGY' ] })
at NullInjector.get (node_modules/@angular/core/fesm2022/core.mjs:1630:27)
at R3Injector.call (node_modules/@angular/core/fesm2022/core.mjs:2159:33)
at R3Injector.apply (node_modules/ng-mocks/index.mjs:1:129465)
at R3Injector.get (node_modules/ng-mocks/index.mjs:1:6416)
at R3Injector.call (node_modules/@angular/core/fesm2022/core.mjs:2159:33)
at R3Injector.apply (node_modules/ng-mocks/index.mjs:1:129465)
at R3Injector.get (node_modules/ng-mocks/index.mjs:1:6416)
at Object.factory (node_modules/@ngxs/store/fesm2022/ngxs-store.mjs:138:44)
at callback (node_modules/@angular/core/fesm2022/core.mjs:2282:47)
at runInInjectorProfilerContext (node_modules/@angular/core/fesm2022/core.mjs:884:9)
My test setup looks like this (commented out code -> previous tries):
describe('TaskSelectionMenuComponent', () => {
let component: TaskSelectionMenuComponent ;
let fixture: ComponentFixture<TaskSelectionMenuComponent >;
// let storeSpy: Store;
let storeSpy: jasmine.SpyObj<Store>;
let configManagerServiceMock: jasmine.SpyObj<ConfigurationManagerService>;
const mockSessionId = 'test-session-id';
const mockResponseSession: ResponseSession[] = [
// default values
];
const defaults: TaskSelectionStateModel = {
isLoading: false,
id: undefined,
state: TaskState.Default,
tasks: [],
error: undefined,
};
beforeEach(() => {
// storeSpy = TestBed.inject(Store);
// storeSpy.reset(defaults);
storeSpy = jasmine.createSpyObj('Store', ['select', 'dispatch', 'selectSignal']);
storeSpy.select.and.returnValue(of(defaults));
storeSpy.dispatch.and.resolveTo(undefined);
storeSpy.selectSignal.and.resolveTo(undefined);
// storeSpy.select.and.returnValue(jasmine.createSpyObj('Signal', ['pipe']));
// storeSpy.dispatch.and.returnValue(jasmine.createSpyObj('Observable', ['subscribe']));
// storeSpy.selectSignal.and.returnValue(jasmine.createSpyObj('Signal', ['subscribe']));
configManagerServiceMock = jasmine.createSpyObj('ConfigurationManagerService', ['getSessions']);
configManagerServiceMock.getSessions.and.resolveTo(mockResponseSession);
TestBed.configureTestingModule({
imports: [TaskSelectionMenuComponent],
providers: [
{ provide: Store, useValue: storeSpy },
{ provide: ConfigurationManagerService, useValue: configManagerServiceMock },
],
}).compileComponents();
TestBed.inject(Store);
TestBed.inject(ConfigurationManagerService);
fixture = TestBed.createComponent(TaskSelectionMenuComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Versions: Angular 19.2.0 NGXS Store 19.0.0
Where am I going wrong? I've checked in the documentation and SO, but I've only found questions about how to unit test the Store itself, not components that use the store. If I've missed something in the docs or somewhere else, please let me know!
Thank you!
I've found the issue- there's a custom Logger that got somehow chained into this component (I assume through the NGXSLoggerPlugin) that I didn't know about before (it's a big codebase and I'm relatively new to the team). Once that was appropriately mocked, the tests worked fine. I've updated the code in my question to comment out/in the code that I'm using currently, in case anyone else is looking for tips on mocking NGXS Store functions.