ngrx / platform

Reactive State for Angular
https://ngrx.io
Other
8.01k stars 1.97k forks source link

RFC: Mock Store #915

Closed TeoTN closed 5 years ago

TeoTN commented 6 years ago

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[x] Feature request
[ ] Documentation issue or request

What is the current behavior?

There's no simple way to mock a Store for testing purposes.

Expected behavior:

When testing a component or an effect, I'd like to be able to inject a fake Store to the tested entity. It became increasingly non-trivial after the pipable operators were introduced.

For instance, one may want to test behavior component with a given state that is a result of many various actions. Similarly, one may want to test an effect with various fixtures for state. Across the web there are multiple snippets with custom implementation of MockStore that extends either Store or BehaviorSubject but all of them are outdated. The presence of such snippets suggests though that there's a real need for some testing utils. I propose introduction of @ngrx/store/testing module which would contain provideMockStore for providing MockStore in place of Store and an implementation of MockStore that provides nextMock method that replaces current state just like BehaviorSubject.next does on Subject.

Version of affected browser(s),operating system(s), npm, node and ngrx:

Newest ngrx, reasonably new npm, node, Chrome and Windows.

Other information:

I'll make a contribution with MockStore class and a provider, if you feel it's a valid issue.

MikeRyanDev commented 6 years ago

Great suggestion! @brandonroberts and I have a mock Store implementation we use internally that works similarly to how you propose. It should also setup Store#dispatch to be a spy.

vitaliy-bobrov commented 6 years ago

Great proposal! IMHO will be nice to have a separate package inside platform with all testing helper tools.

hoangdovan commented 6 years ago

Yeah, We really need it!

alex-okrushko commented 6 years ago

This has been raised a number of times already and I'd like to get the consensus whether mock Store is needed or not :)

E.g. in https://github.com/ngrx/store/issues/128 @robwormald says:

If you want to implement a mock version (which, IMHO, is entirely unnecessary, but feel free...)

The same recommendation is at current testing guide for the store https://github.com/ngrx/platform/blob/master/docs/store/testing.md#providing-store-for-testing

Reducing state is synchronous, so mocking out the Store isn't required.

Internally at Google I've been recommending to avoid mocking the Store as well - my suggestion was to use the real Store and if it's needed to be in certain state for the tests to just dispatch the sequence of actions. But we are using EffectsRunner copied from the previous version of ngrx/effects, so that might be helping us.

TeoTN commented 6 years ago

@alex-okrushko I am perfectly aware of the fact the store is synchronous. However in my opinion, dispatching a sequence of actions (possibly wrapped in effects) only for testing something completely else, is simply writing code that serves no purpose. I think of it as just working around the fact that we have no possibility to simply pass an object with the state snapshot for tests. It's probably a waste of time, programmers' keyboards and CPUs power ;)

I believe that unit tests should just verify very narrow scope of reality and we shouldn't really need to build boilerplate code to make them run, right?

philmerrell commented 6 years ago

@alex-okrushko We can't all be Rob Wormalds. 😉

Whether a mock store is necessary or unnecessary, I think at the very least, the documentation for unit testing around ngrx should define a much broader scope than is currently provided in the testing guide.

The attraction for a mock store for me is ease of use and simplicity. If there is a mock that can be provided in my spec classes that reduces the friction for setting up spies, establishing needed mock state, and testing with effects easier, count me in. Maybe I'm lazy, or I don't understand the inner workings of ngrx sufficiently, but I'd like to make the task of writing tests near brainless. Don't make me think too hard just to scaffold out tests.

Short of providing a mock, better documentation would be great around different scenarios. If that documentation exists... a more obvious link would be great.

blackholegalaxy commented 6 years ago

I'd appreciate a complete testing guide. Testing every part of the store (including router state) AND inclusion in components throught selectors. Testing documention is almost non existent.

honsq90 commented 6 years ago

It's not a full-fledged MockStore, but it's helped quite a bit when I was testing container components that were subscribed via different store selectors. We had issues where we had to initialise nested state in the TestBed, and we already had separate selector tests to cover the selector logic.

The MockStore implementation I wrote allows manual publishing of events per selector, which was useful in testing different permutations in the container component.

// in container component
ngOnInit() {
  this.list$ = this.store.select(getList);
  this.pending$ = this.store.select(getPending);
  this.error$ = this.store.select(getError);
}
// in beforeEach
const mockStore =  new MockStore({ getList, getPending, getError });
spyOn(mockStore, 'select').and.callThrough();
spyOn(mockStore, 'dispatch').and.callThrough();

TestBed.configureTestingModule({
  providers: [
     { provide: Store, useValue: mockStore }
  ]
}

// in test
it('should not show list when pending', () => {
  mockStore.selectStream['getList'].next([{ foo: 'bar' }]);
  mockStore.selectStream['getPending'].next(true);
  mockStore.selectStream['getError'].next(false);
  fixture.detectChanges();

  // do assertions
});

Here are the links to the implementation (my own sandbox repo outside of work).

Hope this helps while an official MockStore is being worked on!

nasreddineskandrani commented 6 years ago

@alex-okrushko

Internally at Google I've been recommending to avoid mocking the Store as well - my suggestion was to use the real Store and if it's needed to be in certain state for the tests to just dispatch the sequence of actions

I am doing it the way you are describing by triggering actions... i don't feel this is the right way (it's eurkk ).

Example: (why i am saying this way is eurkk ^^!!) If you have a common component you need to use in different pages/features. Which action you pick in the tests to set the right state in store (the one from pageA, pageB, featureC, ...)? What happen if you pick pageA action and the product team decide to remove pageA later during the year...? (time lost at least)

I think from what i see in @TeoTN or @honsq90 propositions, there are benefits of mocking directly the store/selectors vs using actions:


@TeoTN i didn't check the PR and test it yet. You are proposing nextMock to update the whole store? seems @honsq90 already implemented a more flexible mocking strategy at first view. Didn't try yet. What do you think about it?

Juansasa commented 6 years ago

@honsq90 Solution however will not work on the new pipeable select

TeoTN commented 6 years ago

@nasreddineskandrani Well, yes, the idea is to use nextMock to update the whole state. Generally, as with all PRs there's an underlying problem I was trying to solve: in a bit more complex applications, building up a desired state for testing would require quite a lot of effort, e.g. dispatching multiple actions etc. Hence, my idea was to be able to simply bootstrap the whole state for testing. Such mocked state could be also extracted and stored in a separate file.

I rely on the fact that State is actually a Subject.

I want to be extra clear on that: it's a dead simple, hammer-like solution. But I'm open for extensions ;)

nasreddineskandrani commented 6 years ago

so @TeoTN can we build a solution to mock selector directly. It's more powerfull, No? I am ready to close this... we need a focused effort to decice and update the code to have it next release PLEASE ^^

TeoTN commented 6 years ago

@nasreddineskandrani that's an interesting idea but what if someone's not using the selector? I mean, it's a layer above the single source of truth and technically one may decide not to use it

Juansasa commented 6 years ago

@nasreddineskandrani I've trying to mock the selectors for a whole day without any success. The new pipeable select is just pain in the ass to mock :/

nasreddineskandrani commented 6 years ago

@TeoTN if he don't use selector from ngrx you mean => I guess he will need to find his own way of testing also :D @Juansasa Sorry i am starting a new job. A lot of efforts... I ll try when i stabilize if no one finish it yet. In around 2 weeks maximum.

TeoTN commented 6 years ago

@nasreddineskandrani lol, that's so inclusive approach

maxime1992 commented 6 years ago

Joining the discussion here after I upgraded a huge project to use the pipeable operator.

I've made my own implementation of MockStore:

export class MockStore<T extends RootState = RootState> extends BehaviorSubject<T> {
  dispatch = jasmine.createSpy();
}

But realised that it is far from ideal when dealing with unit tests (whereas it's just fine for integration tests where you provide the initial state).

For unit tests, I want to be able to return directly a value for a given selector.

So with the help of @zakhenry I came up with the following:

export class MockStore<StateType extends RootState = RootState> extends BehaviorSubject<StateType> {
  private selectorsToValues: Map<(...args: any[]) => any, any> = new Map();

  public dispatch = jasmine.createSpy();

  constructor(initialState: StateType = null, private returnNullForUnhandledSelectors = true) {
    super(initialState);

    spyOnProperty(ngrx, 'select').and.callFake(_ => {
      return selector => {
        let obs$: Observable<any>;

        if (this.selectorsToValues.has(selector)) {
          const value = this.selectorsToValues.get(selector);

          obs$ = value instanceof Observable ? value : this.pipe(map(() => value));
        }

        obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue()))));

        return () => obs$.pipe(distinctUntilChanged());
      };
    });
  }

  addSelectorStub<T>(cb: (...args: any[]) => T, mockedValue: T | Observable<T>): this {
    this.selectorsToValues.set(cb, mockedValue);
    return this;
  }

  setState(state: StateType): this {
    this.next(state);
    return this;
  }

  setReturnNullForUnandledSelectors(value: boolean): this {
    this.returnNullForUnhandledSelectors = value;
    return this;
  }
}

It's very fresh and might need some more work.

Also, notice the spyOnProperty(ngrx, 'select'), I've imported ngrx like that:

import * as ngrx from '@ngrx/store';

Juansasa commented 6 years ago

@maxime1992 Nice spyOnProperty is precisely what i missed, didnt know there are such method in jasmine 👍

maxime1992 commented 6 years ago

@Juansasa took me a while to figure that out too. Had no idea it existed.

Good thing is, I've learned quite a few things in the research process!

With spyOnProperty you can also spy/stub getters and setters :zap:

It seems that the default is setting the get so I just took it out but you can also do spyOnProperty(ngrx, 'select', 'get')

blackholegalaxy commented 6 years ago

Is there any equivalent on jest?

nasreddineskandrani commented 6 years ago

@blackholegalaxy maybe https://github.com/phra/jest/commit/1a3b82a949776971075efeb08210e1bd464a11de

@maxime1992 can you please provide an example of your mockstore on stackblitz?

marcelnem commented 6 years ago

@maxime1992 nice! Does it work universally with the traditional and also pipeable select? Also I think the obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue())))); should be wrapped in the else clause.

marcelnem commented 6 years ago

@maxime1992 I really like your implementation, I have added the store() function to your MockStore code, so is should work with the classic as well as pipeable select operator.

and I have wrapped this piece of code in the else clause: obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue()))));

Now it looks like (code based on code of @maxime1992 from few posts earlier):

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { AppState } from '@app/reducers.index';
import { map, distinctUntilChanged } from 'rxjs/operators';
import * as ngrx from '@ngrx/store';

@Injectable()
export class MockStore<StateType extends AppState = AppState> extends BehaviorSubject<StateType> {
  private selectorsToValues: Map<(...args: any[]) => any, any> = new Map();
  public dispatch = jasmine.createSpy();

  public select = jasmine.createSpy().and.callFake(
    (selector: any): Observable<any> => {
      return this.getObservableWithMockResult(selector).pipe(distinctUntilChanged());
    }
  );

  constructor(initialState: StateType = null, private returnNullForUnhandledSelectors = true) {
    super(null);
    spyOnProperty(ngrx, 'select').and.callFake(_ => {
      return selector => { 
        return () => this.getObservableWithMockResult(selector).pipe(distinctUntilChanged());
      };
    });
  }

  private getObservableWithMockResult(selector:any):Observable<any>{
    let obs$: Observable<any>;

    if (this.selectorsToValues.has(selector)) {
      const value = this.selectorsToValues.get(selector);

      obs$ = value instanceof Observable ? value : this.pipe(map(() => value));
    } else {
      obs$ = this.pipe(map(() => (this.returnNullForUnhandledSelectors ? null : selector(this.getValue()))));
    }
    return obs$;
  }

  addSelectorStub<T>(cb: (...args: any[]) => T, mockedValue: T | Observable<T>): this {
    this.selectorsToValues.set(cb, mockedValue);
    return this;
  }

  setState(state: StateType): this {
    this.next(state);
    return this;
  }

  setReturnNullForUnandledSelectors(value: boolean): this {
    this.returnNullForUnhandledSelectors = value;
    return this;
  }
}

I am using it in tests like this at the moment:

import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
import { StoreModule, Store } from '@ngrx/store';
import { MockStore } from '@app/test-utils/mockup-store';

describe('SomeComponent', () => {
  let component: SomeComponent;
  let fixture: ComponentFixture<SomeComponent>;
  let mockStore: MockStore<ExtendedStateWithLazyLoadedFeatures>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [SomeComponent],
      imports: [StoreModule.forRoot({})],
      providers: [{ provide: Store, useValue: new MockStore<ExtendedStateWithLazyLoadedFeatures>(null, true) }]
    }).compileComponents();
  }));

  beforeEach(inject([Store], (testStore: MockStore<ExtendedStateWithLazyLoadedFeatures>) => {
    // save the automatically injected value so we can reference it in later tests
    mockStore = testStore;
  }));

  describe('with values for selectors defined', () => {
    beforeEach(() => {
      mockStore.addSelectorStub(selector1, {});
      mockStore.addSelectorStub(selector2, ["mockValue"]);
    });

    beforeEach(() => {
      fixture = TestBed.createComponent(SomeComponent);
      component = fixture.componentInstance;
      fixture.detectChanges();
    });

    it('should create', () => {
      expect(component).toBeTruthy();
    });

    // this is checking if the non-pipeable variant of select was called
    it('should call selector selector1', () => {
      expect(mockStore.select).toHaveBeenCalledWith(selector1);
    });

    // this is checking if the non-pipeable variant of select was called
    it('should call selector selector2', () => {
      expect(mockStore.select).toHaveBeenCalledWith(selector2);
    });

  });
});

I am a beginner with Angular tests. Feedback is appreciated.

nasreddineskandrani commented 6 years ago

I took the last version posted by @marcelnem original by @maxime1992 and did a poke with it in stackblitz -> https://stackblitz.com/edit/ngrx-component-testing-simplified?file=src%2Fapp%2Fluffy%2Fluffy.component.spec.ts I didn't check any line of the implementation but it tooks me 5sec to mock the selector using it! YEAHH.

Original @TeoTN proposal can also be added in // to this. One do not exclude the other but this is definitly needed to simplify the smart component testing related to ngrx.

@MikeRyanDev look at it please if it needs code refactoring, Let's proceed :) because it ll make a lot of devs happy mocking selectors like that. And let's check if there is problem or limitation with current proposal.

I am personally suggesting for now the selectors mocking integration to the lib as high priority.

p.s. thank's to all of you for shaking this! i hope it will make it into the lib soon ^^

TeoTN commented 6 years ago

In my opinion @nasreddineskandrani solution is immediately ready for deprecation, as it's not following up to date RxJS standards for pipable operators. Moreover, it'd force people to use selector which is an opt-in helper, not obligatory part of the store. The purpose of this is to let people test components given some fixed state without need to dispatch multiple actions. Mocking the select fn is just a poor workaround.

Think of how you test observables - you'd mock the data stream, not the operators

nasreddineskandrani commented 6 years ago

Moreover, it'd force people to use selector which is an opt-in helper, not obligatory part of the store.

Most of devs use select from @ngrx/store. These devs want an efficient solution (provided also by the lib) to test around select operator.

For example to test the selectors as @timdeschryver explained in this post. https://blog.angularindepth.com/how-i-test-my-ngrx-selectors-c50b1dc556bc the ngrx lib provide Projector possibility (provided internally by ngrx lib).

===> The same thing apply for mocking the selectors.

The purpose of this is to let people test components given some fixed state without need to dispatch multiple actions. Mocking the select fn is just a poor workaround.

Nop! The purpose is to be able to write unit tests or integrated tests easily (Not only for components. Effects also). @maxime1992 solution answer the needs ^^ and needs to be evaluated which i asked Mike for to see if the solution has limitation. You evaluation of his solution is it's not following up to date RxJS standards for pipable operators. Can you please provide example around this statement? to let us understand.

In my opinion @nasreddineskandrani solution is immediately ready for deprecation

you are writing comment, deleting them on this github issue... then closing the issue and reopening it. You should try to understand others needs/problems :( and not push only for your proposal

marcelnem commented 6 years ago

@TeoTN if you want to mock the whole store, and do not want to use selectors. Can't you just use the real ngrx store and dispatch a NextMock action and have a NextMock reducer which replaces the whole state of the store? Is there a performance issue with that? From your first post it is not clear to me why a mock store is needed and a real store cannot be used. Or is there anything else that you expect from a MockStore apart from the nextMock function which you mention?

I see the benefit of mocking just selectors in that I do not have to construct the whole state (which is quite complicated in our application). I only need to mock the response of selectors. For me passing the whole state using nextMock is not always practical.

nasreddineskandrani commented 6 years ago

@marcelnem

I see the benefit of mocking just selectors in that I do not have to construct the whole state (which is quite complicated in our application). I only need to mock the response of selectors. For me passing the whole state using nextMock is not always practical.

exactly! thank you for highlighting this.

TeoTN commented 6 years ago

@nasreddineskandrani I've closed the issue for a way different reason and reopened it after a few minutes, I don't see reason to explain that. I'm not pushing, I think though that what happened here is that a few guys came over there and with barely glancing at the PR they decided to copy-paste their solutions they've used without a word of justification why those would be better. I don't care what worked for you, I do care about why my solution wouldn't work for you and how can I improve that. If you feel yours is better - open up another PR. I don't think that this kind of elbowing is productive nor fruitful.

Mocking selectors one by one doesn't take into account that a piece of an app may use multiple selectors and you'd have to take all the intertwined logic into account while mocking. Contrary to that, mocking the top-level state allows to pass a real snaphot of your environment (if something is missing, it's an error). And it actually needs _only parts of state_that are actually consumed within the testbed. It works like a charm in large, enterprise level apps.

As for pipable operators - lol, I won't comment on that. Do the homework and read why they were introduced. I'd recommend that you actually read source code of the lib you want to write PRs for. -> select deprecation.

@marcelnem no, as it eliminates possibility of doing some integration testing - fake reducers and fake actions don't give you the actual environment. + why would I write that for testing every app that's using ngrx?

Again - you don't test observables by mocking their operators, why would you do this frankenstein logic for the store?

nasreddineskandrani commented 6 years ago

Mocking selectors one by one doesn't take into account that a piece of an app may use multiple selectors and you'd have to take all the intertwined logic into account while mocking.

You can put a breakpoint grab the value at runtime in the last needed selector and use it as a mocked data. Should be fast without thinking about interwired logic.

As for pipable operators - lol, I won't comment on that. Do the homework and read why they were introduced. I'd recommend that you actually read source code of the lib you want to write PRs for. -> select deprecation.

I know about the deprecation of select and ofType. I am using the pipable version of ngrx select in the example i built from @maxime1992 and @marcelnem posts => it's working fine. https://stackblitz.com/edit/ngrx-component-testing-simplified?file=src%2Fapp%2Fluffy%2Fluffy.component.spec.ts

I investigated: ngrx team deprecated the select inside the Store class. https://github.com/ngrx/platform/blob/master/modules/store/src/store.ts#L23 in @maxime1992 code, he is using import * as ngrx from '@ngrx/store'; and spyOnProperty the select outside of the Store class, which means this one: https://github.com/ngrx/platform/blob/master/modules/store/src/store.ts#L115 that's why it's working :)

please be kind in your answers because you never know who don't know ;) you or the others.

TeoTN commented 6 years ago

@nasreddineskandrani If you wanted me to treat you with respect, you should've started with reading my PR. You've admitted not having read the code and started off with something different. Do you even know what you're commenting?

I see at least three major issues with the select-mocking approach:

1. Purpose As you consider promises, thunks, generators, observables and CSP, these are all some wrappers building around a data structure, that will be available in future, possibly changing over time, defered in retrieval, or cancellable, but all in all, the code units you write depend on the wrapped data structure (e.g. current value of the observable). Therefore, when testing the unit, you want to provide a fixture of that piece of data to abstract from any particular use case.

Mocking select is about mocking one particular use case of the surrounding data structure (in this case the observable), whereas mocking the state is about the actual underlaying data.

Please refer to the docs on selectors / advanced usage to see that it clearly states that you may want to use custom operators instead of select (they may be built on top of select but don't have to)

2. Approachability Both the select-mocking and state-mocking approaches are equivalent in terms of ease of building a fixture. In both cases you're mocking only what's needed. For selectors, you're mocking the value returned by the given selector. For state mocking, you're building state snapshot from root's perspective but only include what's needed.

The drawback of select-mocking though is that when testing smart components (possibly in integration testing manner with nested smart components) you have to ensure each and every selector is mocked or explicitly rely on the initial state. In state-mocking you're declarative about what's used.

3. Lightweight You have imported the whole ngrx/store which is unacceptable in systems without tree shaking. State-mocking MockStore is depending only on a few parts. Your implementation for non-observable values would create a brand new observable for every call to select, whereas the state-mocking approach does the thing once in construction time.

marcelnem commented 6 years ago

@TeoTN Piotr, I think that the solutions are complementary. I understand better now that if the logic in selectors is complicated it might be easier to make a snapshot of a store and inject the whole snapshot using the nextMock function.

I agree that the code that @maxime1992 and I have posted does not cover the scenario when somebody wants to use mockStore.pipe(take(1)) or similar ..., It only mocks the select function, the select pipeable operator, and spies on the dispatch function. It is not a complete mock that covers the whole API of the store.

For integration tests (components+selectors+store working together) the nextMock solution is handy. However, for isolated unit tests, if we only have the nextMock solution I would be missing the ability to mock the selectors. If a bug is introduced in a selector function we now might get errors in the component specs. I do not find this ideal when the goal is to have isolated unit tests for components. By being able to mock the returned value of the selector as is possible in the solution of @maxime1992, the tests are isolated. They do not depend on the correctness of selectors.

Additionally, to nextMock and spyOnDispatch, I think, it would be good to add an ability to mock selectors to the proposed MockStore implementation. Being able to mock selectors is mentioned quite often in the thread, so I think people like to use it. It also prevents having to keep and maintain/update snapshots of the whole store.

TeoTN commented 6 years ago

@marcelnem I have to start from the end:

It also prevents having to keep and maintain/update snapshots of the whole store.

There's no need to provide unused parts of the state, you are mocking from root's perspective but omit unused parts :) So you're not maintaining the whole-store snapshots. However, you may need to maintain the initial state but I guess this is something that everyone has, or may obtain with reducer(undefined, {})

I admit I have different approach to testing selectors, presumably not the best practice but I was fine with components failing on errorneous selectors. I consider them a part of the component that was extracted but I see your point too. I am not sure though, why you'd need a mock for select operator, given that the selector function can be mocked itself - probably sth similar to:

import * as fromSelectors from './selectors';
...
spyOnProperty(fromSelectors, 'mySelector').and.returnValue(mockedData$);

Wouldn't this work? If import in the component file wouldn't take this into account (not sure how the spy method is working) you could do this same way as while testing on node - with proxy lib on imports.

nasreddineskandrani commented 6 years ago

You've admitted not having read the code and started off with something different. Do you even know what you're commenting?

true. I subscribed to this issue because i think ngrx lib need the mockStore => how you implement it :) doesn't matter at first place for me. I just retained from the description that provides nextMock method that replaces current state.

let's recap.

@marcelnem

For integration tests (components+selectors+store working together) the nextMock solution is handy.

true! I agree with this. That's why i am saying one solution don't exclude the other (nextMock & selectors mocks).

@marcelnem

I do not find this ideal when the goal is to have isolated unit tests for components.

@TeoTN

I consider them a part of the component that was extracted but I see your point too.

great! we want to isolate unittests. but @TeoTN don't like having the ability of mocking only select operator which is weak for devs that have custom selectors.

Like selectLastStateTransitions from the official doc. (so the remaining problem is here)

UPDATE: no problem @TeoTN your suggestion works for the advanced selectors case! doing this 💃

import * as fromSelectors from './selectors';
...
spyOnProperty(fromSelectors, 'mySelector').and.returnValue(mockedData$);

So for all selectors, we just need to use the select operator only in selectors files to be able to mock easily. With that we don't need to add mocking select operator to ngrx mockStore. You all agree?

marcelnem commented 6 years ago

@nasreddineskandrani

can you post an example how you made it work with the spyOnProperty snippet please?

import * as fromSelectors from './selectors';
...
spyOnProperty(fromSelectors, 'mySelector').and.returnValue(mockedData$);
nasreddineskandrani commented 6 years ago

With that we don't need to add mocking select operator to ngrx mockStore. You all agree?

i think it's not true it moved the problem to the selectors side :) but it's evolving.

@marcelnem I am writing an example on stackblitz. :D give me some time

marcelnem commented 6 years ago

When I tried to use the snippet, I run into the problem that the selector functions would have to be defined as a property of some object. Discussed here: https://github.com/jasmine/jasmine/issues/1415. I did not get over it. Maybe it is easier to spy on property the ngrx/select and spy on Store.select() function as was doing the code from @maxime1992 .

nasreddineskandrani commented 6 years ago

facing the same issue. Checking it

UPDATE: it's workingggg!!!! https://stackblitz.com/edit/ngrx-component-testing-simplified?file=src%2Fapp%2Fluffy%2Fluffy.component.spec.ts

//luffy.component.spec.ts
import * as luffySelectors from './luffy.selector';
...
spyOn(luffySelectors, 'selectSuperPowerFlying').and.returnValue(of(true));
...
//luffy.selector.ts
...
export const selectSuperPowerFlying = pipe(
  select(getSuperPowerFlying)
);
...

This is just awesome ^^ for unit testing components and effects => testing will be faster now without waiting for update of ngrx 💃 ! thanks for all interactions :) we got something 👍

again i think we still need to be able to mock select operator :) since using this strategy solves the problem for unit testing components/effects and maybe moved the problem to the tests of selectors. Also some devs use select directly in the components...


@MikeRyanDev @brandonroberts Can we move forward with @TeoTN PR to have the mockStore and nextMock ability for integration tests as a first step? and tackle the select operator mocking for unit tests in another PR as a second step.

SerkanSipahi commented 6 years ago

Im really happy to see there are growing something which will awesome! I like both approaches. There are cases, which is good to mock the selectors and cases where to mock the store.

Thank you guys !

Update: regardless of the fact that we have now come to a good solution (I hope), I have learned in this tread a lot about to mock the store/selector. This thread is really great. Thanks for that.

marcelnem commented 6 years ago

@nasreddineskandrani Is it necessary to wrap every selector

export const getSuperPowerFlying = createSelector(selectSuperPower, (superPower) => {
  return superPower.fly;
})

into a pipe?

export const selectSuperPowerFlying = pipe(
  select(getSuperPowerFlying)
);
nasreddineskandrani commented 6 years ago

@marcelnem until they provide an official way to mock select. I suggest to use this and to be honest this is more powerful at first view. So yes I guess you have to. If someone has better he can provide his solution 😊 until then this is how am doing it from now.

The most important is to make the testing easier (I hope this become a priority soon for ngrx team since companies are suffering). With this we don.t need to trigger action to put the store in the right state to be able to unit tests. A huge time saved.

SerkanSipahi commented 6 years ago

@marcelnem it is possible to create in the meantime a repo for your suggested approach so that we can work (improvements, tests, fixing bugs, etc) on it and when the approach from @TeoTN is already done (i guess in v7 of ngrx) then we can think about it to merge the mocking of selector into @TeoTN suggested approach. Why im saying that? I would like to able, to get the newest changes, bug fixes, etc.

marcelnem commented 6 years ago

I understand. I prefer to mock the selectors in some (most of the) situations (unit test of container components). Mocking the whole state or part of it is another tool which I also find useful in some cases (e.g. testing the selectors themselves), so the contribution of @TeoTN is valuable. But for isolated unit tests of containers, I think it is good to be able to mock the ngrx/select operator directly. By approving the PR without an ability to mock select, this might lead many people to mock the whole state in all cases just because this will be the only option in with the to-be-official MockStore.

Wrapping every selector into a pipe and adding a boilerplate code into each selectors.ts code just to be able to mock them is tedious. I find it better to mock the ngrx/store directly as in the code from @maxime1992.

SerkanSipahi commented 6 years ago

@nasreddineskandrani hey sorry, see my last post! i mean you and not @marcelnem with it is possible to create in the meantime a repo .... :) Can you create some repo?

nasreddineskandrani commented 6 years ago

I recommend to wait to see what ngrx team think about the current proposal(s). Before going and create a public repo in // to maintain a temporary MockStore out of the official lib. The right move is to get things done, approved and merged. Be patient!

SerkanSipahi commented 6 years ago

yes you are right. I was to hasty!

SerkanSipahi commented 6 years ago

@nasreddineskandrani This does not work for me: spyOn(selectors, 'someSelector').and.returnValue('foo');

I get following error:

Error: <spyOn> : someSelector is not declared writable or has no setter

How did you do that it works for you? How can i make it writeable? my selectors are declared as const but changing let let same error.

nasreddineskandrani commented 6 years ago

@SerkanSipahi you need to build the selector with pipe(select... and add of( ) in the returnValue. Please check the online example i posted.

SerkanSipahi commented 6 years ago

I checked the online example. I took an example which is not built with pipe (it works in online example):

luffy.selector.ts

export const selectPowerA = createSelector(selectLuffy, (luffyState) => {
  return luffyState.powerA;
});

luffy.component.spec.js

spyOn(luffySelectors, 'selectPowerA').and.returnValue(of(true));

Error:

not error on your example
nasreddineskandrani commented 6 years ago

@SerkanSipahi GG! i assumed it's not working without pipe but it's even more simple 💃 so why are we trying to mock select :D.

With my friends, we didn't try this at first place following the doc with action triggers. But it was none productive that's why i was hunting a better solution.

So i guess an update is needed with this way of mocking selectors into the ngrx testing documentation. For the doc update:

Question: this kill the need of mocking select in mockStore or someone see a case?