I am facing a challenge with an unusual behavior during the unit testing of my NGXS store using Jasmine.
Specifically, I am encountering issues when trying to test the DeleteAlerts action :
@Action(DeleteAlerts)
deleteAlerts(ctx: StateContext<AlertStateModel>, action: DeleteAlerts) {
return this.alertService.deleteAlerts(action.alertIds).pipe(
map(response => {
if (response.ok) {
action.alertIds.forEach(alertId => {
ctx.setState(patch({
alerts: removeItem<UniqueAlert>(alert => alert.alertIds.includes(alertId))
}));
});
} else {
throw new Error('Server failed to respond.');
}
}
));
}
However, prior to testing the DeleteAlerts action, dummy data needs to be populated in the store.
To achieve this, I have created a mock :
const alertsMock = new Alerts({
alerts: [new UniqueAlert({alertIds: ['test1']}),
new UniqueAlert({alertIds: ['test2']}),
new UniqueAlert({alertIds: ['test3']})]
});
The structure of my store is as follows :
export interface AlertStateModel {
alerts: UniqueAlert[];
}
When attempting to populate the store with the mock data :
store.reset({alerts: alertsMock.alerts})
Unfortunately, in my testing scenario, the DeleteAlerts
action does not get dispatched upon calling
store.dispatch(new DeleteAlerts(alertIds))
An intriguing observation is that the action gets dispatched successfully if I replace the store.reset
method with a dispatch for GetAlerts
, which fetches alerts from a mocked service :
The GetAlerts action :
@Action(GetAlerts)
public getAlerts(ctx: StateContext<AlertStateModel>) {
return this.alertService.getAlerts().pipe(
tap(fetchedAlerts => {
ctx.setState({alerts: fetchedAlerts.alerts});
})
);
}
The following test passes without any issues :
it('should delete one alert from the store when DeleteAlerts is dispatched', () => {
spyOn(alertService, 'getAlerts').and.returnValue(of(alertsMock));
store.dispatch(new GetAlerts());
spyOn(alertService, 'deleteAlerts').and.returnValue(of(new HttpResponse({status: 200})));
store.dispatch(new DeleteAlerts(['test2']));
store.selectOnce(AlertState).subscribe(data => {
expect(data.alerts).toEqual(alertsMock.alerts.filter(alert => !alert.alertIds.includes('test2')));
});
});
});
Conversely, this test doesn't yield the expected results :
it('should delete one alert from the store when DeleteAlerts is dispatched', () => {
store.reset({alerts: alertsMock.alerts});
spyOn(alertService, 'deleteAlerts').and.returnValue(of(new HttpResponse({status: 200})));
store.dispatch(new DeleteAlerts(['test2']));
store.selectOnce(AlertState).subscribe(data => {
expect(data).toEqual(alertsMock.alerts.filter(alert => !alert.alertIds.includes('test2')));
});
});
In addition, it's worth noting that the expectation is set on data
instead of data.alerts
in the non-functional test. This aspect adds to my confusion as the selector should ideally return the state containing a nested alerts
object.
Why are the two tests producing different outcomes, and why does the selector not return the anticipated object when utilizing store.reset
for populating the store?
Regarding the inclusion of a nested alerts
object within the alertsMock
; this format mirrors the data structure returned by the alertService.