Mocking ngrx/store

若如初见. 提交于 2019-12-08 18:13:34

问题


This is in regards to the Angular 2 official release. I know that unit testing has changed drastically between beta, RC, and the official release.

What's a good way to mock @ngrx/store in a unit test when it's used as a parameter in a constructor? It's not as simple as mocking a service.

For example, if I wanted to mock a service, then I could do something like this:

let serviceStub = { }; // not a true mocked service, just a stub, right?

let de: DebugElement;
let el: HTMLElement;
let nativeEl: Element;
let comp: Typeahead;
let fixture: ComponentFixture<Typeahead>;

describe('Component:Typeahead', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [...],
            declarations: [Typeahead],
            providers: [
                {provide: TypeaheadService, useValue: serviceStub} // provides the service that is being "mocked"
            ]
        }).compileComponents();

        fixture = TestBed.createComponent(Typeahead);
        nativeEl = fixture.nativeElement;
        comp = fixture.componentInstance;
        de = fixture.debugElement;
    });
});

And this works.

For ngrx/store, however, it does not (if you substitute Store in for TypeaheadService). I'm thinking you have to write a mock class that extends Store, and then provide that to the component that is being tested, but I'm not sure why that is the case (if that is even the case).

I'm just confused as how to mock ngrx/store in my unit tests and couldn't find any documentation on their site or github. Maybe I overlooked it.


回答1:


Thank you for posting the question and suggesting a potential solution!

The way I've mocked it is, to use the actual actions to set an initial state i.e. a mocked state before each test. Here's an example

beforeEach(inject([Store], (store: Store<ApplicationState>) => {
const someFakeState = {
    counter: 9,
    counterFilter: 'A_FAKE_COUNTER_FILTER'
};

store.dispatch(new myActionToSetSomeData(someFakeState));
}));

Inside your it() block you should now be able to check that the component is displaying a count of 9 and a filtering by 'A_FAKE_COUNTER_FILTER'.

You can of course set the state inside your it block, rather than beforeEach, as long as its before the component is instantiated.




回答2:


You can use forRoot (>= v4) or provideStore ( <= v3) to provide the data to the StoreModule and the rest is done for you:

1 - Import it:

import { StoreModule } from '@ngrx/store';

2 - Create a mock data:

/*
* Mock data
*/
const PAINTS = [];

3 - Import it in you test:

beforeEach(async(() => {
  TestBed.configureTestingModule({
    imports: [ StoreModule.forRoot(PAINTS) ]
  })
}))

In previous versions (before v4), you should use provideStore(PAINTS) instead of forRoot(PAINTS). See the the changelog here




回答3:


Yes, you do have to mock ngrx/store, but not only Store. Store expects three arguments; one of type Observable, and two of type Observer, which is an interface. So, I tried two things. Passing in null values to the StoreMock super() constructor, but that failed at my assertion. My other solution was to implement the Observer interface with a mock class (Observable in this case). This way I could pass defined values into the super StoreMock constructor.

This is just an illustrative example. The ObservableMock doesn't actually mock any functionality that I'm trying to test in my application. It's serving as an enabler so that Store can be injected as a provider into the Component I'm trying to test.

Since Observer is an interface, you have to implement its function declarations in the mock: next, error, and complete.

class ObservableMock implements Observer<any> {
  closed?: boolean = false; // inherited from Observer
  nextVal: any = ''; // variable I made up

  constructor() {}

  next = (value: any): void => { this.nextVal = value; };
  error = (err: any): void => { console.error(err); };
  complete = (): void => { this.closed = true; }
}

let actionReducer$: ObservableMock = new ObservableMock();
let action$: ObservableMock = new ObservableMock();
let obs$: Observable<any> = new Observable<any>();

class StoreMock extends Store<any> {
  constructor() {
    super(action$, actionReducer$, obs$);
  }
}

And now you can add Store as a provider in your Component's test module.

describe('Component:Typeahead', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [...],
            declarations: [Typeahead],
            providers: [
                {provide: Store, useClass: StoreMock} // NOTICE useClass instead of useValue
            ]
        }).compileComponents();
    });
});

I am sure there are other ways to do it. So if anyone has any other answers, please post them!



来源:https://stackoverflow.com/questions/40857864/mocking-ngrx-store

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!