I'm using a SignalStore
from @ngrx/signals
in an Angular project.
My store has onInit
and onDestroy
hooks invoking methods from a dependency injected in the store.
import { signalStore, withHooks } from '@ngrx/signals';
class SomeDependency {
start() {
throw new Error('real implementation not relevant');
}
stop() {
throw new Error('real implementation not relevant');
}
}
const MyStore = signalStore(
withHooks((_store, dependency = inject(SomeDependency)) => ({
onInit() {
dependency.start();
},
onDestroy() {
dependency.stop();
},
})),
);
I'm able to test that dependency.start()
is properly invoked when creating the store
it('should start dependency when created', () => {
const fakeDependency = { start: jest.fn(), stop: jest.fn() };
TestBed.configureTestingModule({
providers: [MyStore, { provide: SomeDependency, useValue: fakeDependency }],
});
TestBed.inject(MyStore);
expect(fakeDependency.start).toHaveBeenCalledOnce(); // test pass ✅
});
I am looking for a way to test that dependency.stop()
is invoked when the store is somehow destroyed, but how can I force the store destruction?
it('should stop dependency when destroyed', () => {
const fakeDependency = { start: jest.fn(), stop: jest.fn() };
TestBed.configureTestingModule({
providers: [MyStore, { provide: SomeDependency, useValue: fakeDependency }],
});
const store = TestBed.inject(MyStore);
// MISSING: force store descruction here
expect(fakeDependency.stop).toHaveBeenCalledOnce(); // test fail ❌
});
As proposed by Naren, creating a testing host solution does the trick.
class SomeDependency {
stop() {
throw new Error('real implementation not relevent');
}
}
const MyStore = signalStore(
withHooks((_store, dependency = inject(SomeDependency)) => ({
onDestroy() {
dependency.stop();
},
})),
);
describe('standard way, using TestBed', () => {
@Component({
standalone: true,
providers: [MyStore],
})
class HostComponent {
store = inject(MyStore);
}
it('should stop dependency when destroyed', () => {
const spy = jest.fn();
const fixture = TestBed.configureTestingModule({
imports: [HostComponent],
providers: [{ provide: SomeDependency, useValue: { stop: spy } }],
}).createComponent(HostComponent);
fixture.destroy();
expect(spy).toHaveBeenCalledOnce();
});
});
Also, using ng-mocks
library can reduce boiler plate and also use a mocked HostComponent. That would be my preferred solution.
describe('alternative with ng-mocks', () => {
@Component({
standalone: true,
providers: [MyStore],
})
class HostComponent {
store = inject(MyStore);
}
MockInstance.scope();
beforeEach(() => MockBuilder(MyStore).mock(SomeDependency));
it('should stop dependency when destroyed', async () => {
const spy = MockInstance(SomeDependency, 'stop', jest.fn());
const fixture = MockRender(HostComponent);
fixture.destroy();
expect(spy).toHaveBeenCalledOnce();
});
});