-
Notifications
You must be signed in to change notification settings - Fork 311
Make mocking easier #128
Comments
What are you trying to test that makes you mock the store? |
I have some services as wrapper around my store and would like to test these services. When other component are using this service I need a Noop Store to simplify the creation of mockups for this service. |
IMO testing a service is rather an integration test than a unit test. You can test the reducer functions and effects on their own in solitary unit tests. In those tests it would make sense to mock things. |
I think if I can isolate the service, it will be a unit test ;) ... and I think it might be helpful to test the service. E.g. I made a lot of stupid mistakes because the payloads are not type safe. In another part of my code I build some complex observable flows and it would be helpful to mock the select function. I often try to reorganize my code so that I do not have to test the integration parts of my application. But in most cases, when I need a debugger, I made a mistakes in these parts. But furthermore, I think that architectures that use services and DI might get very complex dependency hierarchies and therefore it is always very helpful to have the ability to mock dependencies. I don't know if there is some advice for angular2 in general but for me it seems that mocking services that have other dependencies or do some work in the constructor is very painful. In other languages there is the rule to use interfaces for all services. |
Ok I see your point. But it should be possible to provide a MockStore by adding this to your provide(Store, useClass: MockStore) MockStore should extend Store and overwrite the needed parts for your test. The dependencies of the Store can be provided like this as well. |
The problem is: I need to call super in the constructor, therefore I need at least a State reference and some other dependencies. I can have a look to the implementation, which tells me that all other dependencies, but this I need to check it again with every update. Same problem with TypeMoq ( https://github.com/florinn/typemoq) |
Yes that is true. But again, I think it makes no sense to mock the store. When you tested the reducer for your service and provide the store with only that reducer it should be sufficient. |
As I mentioned: I want to test my integration code. I think there are only these options:
I just don't see why you don't like it to have an interface. Using Interfaces is a well known principle for Java, C# and so on and this gives everybody the freedom to organize the tests how he wants. I think there should be some guidelines how to design services in an angular2 app in general. It is not an good situation when some services can be mocked easily and some services can not. |
There speaks nothing against interfaces. :) |
Totally agree :) |
Maybe you could send a PR with the Store implementing such an interface? |
Store already provides an interface, simply by the virtue of being a typescript class. We don't typically use interfaces in typescript, because they have no runtime value and thus aren't usable for dependency injection like other languages, so it's unnecessary overhead that provides little benefit. If you want to implement a mock version (which, IMHO, is entirely unnecessary, but feel free...) import {Store} from '@ngrx/store'
export class MockStore implements Store {
//implement your mock methods here.
}
provide(Store, {useClass: MockStore}) I'd suggest starting your mock store by perhaps extending an Rx Subject/BehaviorSubject, because that's what Store already does. At this point though, you're basically reinventing store entirely. export class MockStore extends Subject implements Store {} |
Awesome, havent realized this yet, thanks. |
Sorry to be bringing this back up when it's been closed for a while. In the code, I don't see an interface for store to implement, would there be a reason to extend Observable for a mock store over the idea of just extending Store to make a mock? For my situation, I've got a navigation service that doesn't perform any http interactions, but handles knowing the state via the ngrx/Store, then dispatching an action to change the panels shown in the ui components. For testing, then I would mock a store that I override the dispatch function to spy on and know that the right action was dispatched. Or would it be better to not mock the store at all, just make a spy, say as
I guess I'm trying to figure out what would be workable and a better approach overall without rewriting much. |
Another way I have seen people stub the store in their TestBed.configuration.providers is as follows:
Basically, you are telling jasmine to create a spy and handle the Store methods you are using in your Service, Component, etc. Just swap dispatch with whatever store method you want and jasmine will say the method exists for your fake Store, but nothing will happen. Keep in mind that any effects from your method being called will not happen. In my case this was fine since I wasn't testing the result of a dispatch, only that it was being called. |
Maybe you should look at the problem from another perspective, and create one easy testable "dumb" component with all input data handled on silver plate, and "controler" for that service connected with store and providing all necessary data for the dumb component. |
@jonesmac how would you test the result after fake |
@kuncevic that is correct. If you want to see the results of dispatch and you need to set up your actual store with the provideStore method. |
@kuncevic One scenario would be if you have a component that both calls dispatch to update a store and receives updates from the store. In that case, you need to provide a store (actual or mock) if you want to test that your component can both trigger and receive store updates. Obviously this would be more of an integration test. You could simply test that dispatch is called and test the part of the component that receives store updates separately.
Hope that helps.
…___________________
Matt Jones
[email protected]
On Jan 6, 2017, 6:57 PM -0500, Aliaksei Kuncevič ***@***.***>, wrote:
@jonesmac ehh it might overcomplicate the test case a bit. Any reasons not go this way? As you saying you just not checking the store at all.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
For my projects, I use the following mock : export class MockStore<T> extends Store<T> {
private _fakeData: Object = {};
private fakeDataSubject: BehaviorSubject<Object> = new BehaviorSubject(this._fakeData);
select = <T, R>(mapFn: any, ...paths: string[]): Observable<R> => {
return map.call(this.fakeDataSubject, mapFn);
}
constructor() {
super(dispatcherMock, reducerMock, stateMock);
}
nextMock(mock: Object, ...keys: string[]) {
let curMockLevel = this._fakeData = {};
keys.forEach((key, idx) => {
curMockLevel = curMockLevel[key] = idx === keys.length - 1 ? mock : {};
});
this.fakeDataSubject.next(this._fakeData);
}
get fakeData() {
return this._fakeData;
}
} FYI : I used the 'fakeDataSubject' instead of the store itself in order to directly send the desired data, without being restricted by the typescript Action class. Given that we don't have any reducer here, I don't see the point to use my actions classes, and directly send a data I would expect to get in a select callback argument. If I need to test dispatch, I simply use : beforeEach(inject([Store], (_store: Store) => {
store = _store;
storeSpy = spyOn(store, 'dispatch').and.callFake((arg) => {
testArg = arg;
});
}));
it('foo bar description', () => {
// some behaviour including a dispatch
expect(storeSpy).toHaveBeenCalled();
expect(testArg).toEqual(new MyAction('foo'));
});
}); and if I need to test select, I write a : beforeEach(()=>{
store.nextMock(mockData, 'storeName', 'subStoreName')
}); Given that I also test my effects, reducers and services, I never use them when testing my components, and so I don't need to have any "from dispatch to select using an action" behaviour with my MockStore. Hope I could help. If you think I could do better, please tell me. And if you think I don't (we can always dream), I think this kind of class could be direcly exported by @ngrx/store, and it would be a pleasure to make a PR with that. |
@noelmace how do you mock:
in your example? |
Hi all!
So my Mockstore class looks following:
However when I try to define my service in test following way it says
This failure is caused by following line in TestedService constructor:
Commenting imports out doesn't help either. Does anyone has ideas what's wrong with mocking? |
I have created a Gist to show how I test my store, I used what @noelmace published. It works well for the select, I didn't try to dispatch because we still need to know what is
like @crain mention Gist: https://gist.github.com/Tuizi/e2842f6ce8cd8bec58df99ef7ec42f9a |
Hi all, |
I did like what @crain was doing, but couldn't get it to work either. So I created an even simpler version that I can subscribe on, and feed some mockdata:
|
Hi jecede, |
Hi,
I am new to angular, typescript and ngrx. I would like to make testing easier. But when I try to mock the store with TypeMoq I end up in a dependency hell. Can you provide an abstract class for your store with a private implementation?
The text was updated successfully, but these errors were encountered: