New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add testing guide for NgRx SignalStore #4206
Comments
Can take care of it. Any specific best practices you would like to be included? |
Can I join in this? |
@rainerhahnekamp sure. We need to clarify whether @markostanimirovic has some specific requirements he would love to put on that page |
Hey 👋 Here are the main topics I'd like to be covered:
Optionally:
For more inspiration check how SignalStore Core Concepts and Custom Store Features pages are structured (attention to details, examples, etc.). |
Okay, about testing without TestBed: I think the number of stores without DI, will be a minority. You will very likely have an I would include an example without TestBed at the beginning, but I'd see the main focus on test cases with DI. When it comes to mocking, we should also have a chapter on mocking the Store itself when it is used inside a component.
What do you think? |
Sounds good to me 👍 |
Excited for this documentation to land. |
Just one addition: I think we should also include some type testing. I find that important for advanced use cases. |
excited as well for this documentation.
|
Hi, you want to create a spy on What I would recommend instead, that you don't spy on the packState but check against the value of the state itself. So something like: Store: const CounterStore = signalStore(
{providedIn: 'root'},
withState({counter: 1})
); Service which you want to test: @Injectable({providedIn: 'root'})
export class Incrementer {
store = inject(CounterStore);
increment() {
patchState(this.store, value => ({counter: value.counter + 1}));
}
} The actual test: it('should verify that increment increments', () => {
const counterStore = TestBed.inject(CounterStore);
const incrementer = TestBed.inject(Incrementer);
expect(counterStore.counter()).toBe(1);
incrementer.increment();
TestBed.flushEffects();
expect(counterStore.counter()).toBe(2);
}) I didn't try out that code. Might include even some syntax errors, but that's the general approach I would recommend. Is it what you had in mind? |
ok that's clear, what about spy on injected services in withMethods . I want to check that a service is called .
|
In the same way as you do for any other service using TestBed.configureTestingModule({
providers: [MyStore, { provide: MyService, useValue: { doSomething: jest.fn() } }],
});
const myStore = TestBed.inject(MyStore);
const myService = TestBed.inject(MyService);
myStore.doSomething();
expect(myService.doSomething).toHaveBeenCalled(); |
I'm trying to implement a helper method to generate an rxMethod spy but stuck on making it work without manual typecasting etc.: import type { Signal } from '@angular/core';
import type { Observable, Unsubscribable } from 'rxjs';
// This reproduces the same types from @ngrx/signals (which aren't exported)
type RxMethodInput<Input> = Input | Observable<Input> | Signal<Input>;
type RxMethod<Input> = ((input: RxMethodInput<Input>) => Unsubscribable) & Unsubscribable;
export const buildRxMethodSpy = <Input>(name: string) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const rxMethodFn = (input: RxMethodInput<Input>) => {
return {
unsubscribe: () => {
return;
},
};
};
rxMethodFn.unsubscribe = () => {
return;
};
const spy = jasmine.createSpy<RxMethod<Input>>(name, rxMethodFn);
// Somehow add `.unsubscribe` to the spy in a way that TypeScript understands
return spy;
}; Note: I already tried using Update: for now I'm resorting to explicit typecasting: import type { Signal } from '@angular/core';
import { noop, type Observable, type Unsubscribable } from 'rxjs';
// This reproduces the same types from @ngrx/signals (which aren't exported)
type RxMethodInput<Input> = Input | Observable<Input> | Signal<Input>;
type RxMethod<Input> = ((input: RxMethodInput<Input>) => Unsubscribable) & Unsubscribable;
export const buildRxMethodSpy = <Input>(name: string) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const rxMethodFn = (input: RxMethodInput<Input>) => {
return {
unsubscribe: noop,
};
};
rxMethodFn.unsubscribe = noop;
const spy = jasmine.createSpy<RxMethod<Input>>(name, rxMethodFn) as unknown as jasmine.Spy<
RxMethod<Input>
> &
Unsubscribable;
spy.unsubscribe = noop;
return spy;
}; But would very much like to hear if there's a better way to do this. |
@jits it looks like you want to spy on the Store's method itself, right? The example you are referring to, is about a service which is used in the Store, and you want to spy on a method on that service. |
Hi @rainerhahnekamp — ahh yes, good spot, thanks. I missed that aspect. (I'll update my comment to reflect this). I don't suppose you have a good way to build a mock or spy for an |
@rainerhahnekamp — that sounds great! (I'll continue with additional thoughts there) |
Information
The new page should be created for the testing guide:
@ngrx/signals
>SignalStore
>Testing
The guide should explain how SignalStore should be tested with examples.
Documentation page
No response
I would be willing to submit a PR to fix this issue
The text was updated successfully, but these errors were encountered: