Reputation: 247
I want to unit test a component. I am using @ngrx/store and rxjs in order to update my store from my component. To do that, I subscribe and unsubscribe to a store. My unit test is failing with
TypeError: undefined is not an object (evaluating 'this.store.unsubscribe') in config/spec-bundle.js
I am not able to find how to correctly mock my store and its event emitters.
Here are the following lines:
app.component.ts :
ngOnDestroy() {
// unsubscribe the observable
this.userStore.unsubscribe();
}
app.component.spec.ts :
how I mock the store :
export class MockStore extends Subject<fromRoot.State> implements Store <fromRoot.State> {}
how I configure :
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
StoreModule.provideStore(reducer)
],
declarations: [LoginComponent],
providers: [
BaseRequestOptions,
MockBackend,
{ provide: AuthService, useValue: authServiceStub },
{ provide: LoginService, useValue: loginServiceStub },
{ provide: LoggerService, useValue: loggerServiceStub },
{
provide: Http, useFactory: (backend: ConnectionBackend,
defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}, deps: [MockBackend, BaseRequestOptions]
},
{ provide: Router, useClass: MockRouter }
]
})
Tell me if you want more informations. Thanks in advance.
Upvotes: 4
Views: 8317
Reputation: 58440
There's more than one way that you can mock the store and what you do is going to depend upon how you want to test.
When I mock the store in my tests, I'm only interested in two things:
I'm not interested in wiring up reducers, as they are tested elsewhere, so this is what I do.
I use a function like this to create a Store
from two Subject
instances:
import { Action, Store } from "@ngrx/store";
import { Subject } from "rxjs/Subject";
export function mockStore<T>({
actions = new Subject<Action>(),
states = new Subject<T>()
}: {
actions?: Subject<Action>,
states?: Subject<T>
}): Store<T> {
let result = states as any;
result.dispatch = (action: Action) => actions.next(action);
return result;
}
And in a beforeEach
I create the Store
and add it to the TestBed
's providers
:
actions = new Subject<Action>();
states = new Subject<AppState>();
store = mockStore<AppState>({ actions, states });
TestBed.configureTestingModule({
imports: [EffectsTestingModule],
providers: [
{
provide: Store,
useValue: store
},
...
]
});
With the mocked Store
injected in the components - or effects, or whatever you are testing - it's then simple to emit a particular state within a test - using the states
subject - and it's easy to check to see that any expected actions have been dispatched - by subscribing to the actions
subject.
Regarding your error, you should not be calling unsubscribe
on the Store
; you should call it on the Subscription
object that's returned when you subscribe to the store - as mentioned in the other answer.
Upvotes: 9
Reputation: 31
The issue might be with your unsubscribe. You should be calling unsubscribe on your actually subscription instead of your observable/store. eg. if you have subscription like this:
this.subscription = this._store
.select('users')
.subscribe(users => {
this.users = users;
});
you unsubscribe like this:
ngOnDestroy() {
// unsubscribe the observable
this.subscription.unsubscribe();
}
Also, not sure if you left out some code, but the way you mocked your store, it'd probably be sufficient to just use the actual store, and just spy on dispatch etc.
Upvotes: 1