问题
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