Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock @ngrx/store?

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.

like image 388
Megan Avatar asked Dec 23 '22 19:12

Megan


1 Answers

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:

  • being able to easily emit a state that's relevant to the test; and
  • being able to see any actions that are dispatched to the store during the test.the

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.

like image 136
cartant Avatar answered Dec 31 '22 14:12

cartant