Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not able to get to actions via props() via shallow #2449

Closed
dschinkel opened this issue Sep 19, 2020 · 16 comments
Closed

Not able to get to actions via props() via shallow #2449

dschinkel opened this issue Sep 19, 2020 · 16 comments

Comments

@dschinkel
Copy link

dschinkel commented Sep 19, 2020

prior to redux 7:

it('contains expected actions', () => {
  const homePage = shallow(<HomePageContainer store={store} />);
  
 expect(homePage.props().fetchFeaturedCompanies).to.exist;
  expect(homePage.props().fetchCompanies).to.exist;
});

after Redux 7 I don't think you can just pass in store as a prop anymore. I thought that they added back that ability? I don't remember where they landed with that.

So since I was getting an error that store is not a property, I had to wrap this in a garbage provider to even get this to compile again after upgrading Redux:

But after doing so, TS complains that store is not a prop:

it('contains expected actions', () => {
  const homePage = shallow(<Provider store={store}><HomePageContainer /></Provider>);

  expect(homePage.props().fetchFeaturedCompanies).to.exist;
  expect(homePage.props().fetchCompanies).to.exist;
});

expected undefined to exist

Here's yet another old test that tries to get to an action via props

  it('fetches companies', async () => {
    mockGet('/api/v1/companies', {});

    const homePage = shallow(<HomePageContainer store={store} />);
    await homePage.props().fetchCompanies();
    const newState = store.getState();

    expect(newState.company.companies).to.exist;
    expect(newState.company.featuredCompanies).to.not.exist;
  });

so it no longer is able to get at props this way or something after Redux 7?

Sigh it's been a while, totally forgot this thread reduxjs/react-redux#1161

can anyone fill me in on the above, if this is even possible in Redux 7 both sending in a store as a prop and also being able to access actions like this as my existing tests were doing with props()?

My preference is:

  • not to go through UI wiring like button clicks
  • not to use mount

I like testing the surface area via props.

@ljharb
Copy link
Member

ljharb commented Sep 19, 2020

TS isn't JS, and enzyme ships no types, so any type system errors are wildly out of scope of this project.

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

So just wait..I had several questions in here...one of which was should we still be able to access props like this through enzyme shallow after redux 7. that's a big question here as well as the passing of store as props period via shallow without provider via Redux 7, and trying provider did not work so far.

So there's a lot more than just "TS" going on here.

@ljharb
Copy link
Member

ljharb commented Sep 19, 2020

You want:

const homePage = shallow(<HomePageContainer />, {
  wrappingComponent: Provider,
  wrappingComponentProps: { store },
});

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

tried that and got

Error: Could not find "store" in the context of "Connect(HomePageContainer)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider>

store.tsx

const middleware = applyMiddleware(thunk);
const initialState = {};
const store = createStore(rootReducer, initialState, middleware);

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, Action<string>>;

export default store;
  it('contains expected actions', () => {
    const homePage = shallow(<HomePageContainer />, {
      wrappingComponent: Provider,
      wrappingComponentProps: { store }
    });

    expect(homePage.props().fetchFeaturedCompanies).to.exist;
    expect(homePage.props().fetchCompanies).to.exist;
  });

Second try:

const dummyStore = (state = {}) => ({
  default: () => {},
  subscribe: () => {},
  dispatch: () => {},
  getState: () => ({ ...state }),
});

it('contains expected actions', () => {
  const homePage = shallow(<HomePageContainer />, {
    wrappingComponent: Provider,
    wrappingComponentProps: { dummyStore }
  });

  expect(homePage.props().fetchFeaturedCompanies).to.exist;
  expect(homePage.props().fetchCompanies).to.exist;
});

Error: Cannot read property 'getState' of undefined

HomePageContainer.tsx

class HomePageContainer extends Component<PropsFromRedux> {
  async componentDidMount() {
    const { fetchFeaturedCompanies, fetchCompanies, fetchCountries } = this.props;
    await fetchFeaturedCompanies();
    await fetchCompanies();
    await fetchCountries();
  }

  render() {
    const { companies, countries, featuredCompanies } = this.props;

    return (
      <HomePage
          className="ft-homepage"
          companies={companies}
          countries={countries}
          featuredCompanies={featuredCompanies} />
    );
  }
}

const mapState = (state: RootState) => ({
  countries: state.country.countries,
  companies: state.companies.companies,
  featuredCompanies: state.companies.featuredCompanies
});

const mapDispatch = {
  fetchCountries,
  fetchCompanies,
  fetchFeaturedCompanies
};

const connector = connect(
  mapState,
  mapDispatch
);

type PropsFromRedux = ConnectedProps<typeof connector>
export default connect(mapState, mapDispatch)(HomePageContainer);

@ljharb
Copy link
Member

ljharb commented Sep 19, 2020

{ store: dummyStore }, probably?

In this case tho, you're using connect, so i think you don't pass a store at all, you just use .dive().

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

with { store: dummyStore }

TypeError: store.getState is not a function

You do have to pass a store with a connect container or it'll complain that it can't find one from context or props. I was passing store via props for the past 5 years, when this stuff was all happy and working prior to Redux 7

I really truly do hate TS 👿

it('contains expected actions', () => {
  const homePage = shallow(<HomePageContainer />).dive();

  expect(homePage.props().fetchFeaturedCompanies).to.exist;
  expect(homePage.props().fetchCompanies).to.exist;
});

Screen Shot 2020-09-19 at 2 25 23 AM

TSError: ⨯ Unable to compile TypeScript: src/test/unit/components/homepage/Home.spec.tsx(57,27): error TS2339: Property 'fetchFeaturedCompanies' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'. src/test/unit/components/homepage/Home.spec.tsx(58,29): error TS2339: Property 'fetchCompanies' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'.

good luck making that "happy" because my component is using

type PropsFromRedux = ConnectedProps<typeof connector>

sigh, just forget it...this is becoming one pile of smelly poop.

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

closing it, thanks for your efforts @ljharb I'm spent...giving up on this for now.

@ljharb
Copy link
Member

ljharb commented Sep 19, 2020

TS has nothing to do with it. Figure out what makes your tests pass, and then use ts-ignore as needed until the real code passes the typecheck.

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

Like I said this code when it was using store as props (without provider) was working just fine for years. The real questions had to do with Enzyme + React-Redux 7 combo and where enzyme lies with all that. TS is an overall major pain in the ass lets be honest.

One more variation I tried just for the records, and again connect does need a store or it'll complain:

Part of the error I had missed for { store: dummyStore } was:

Warning: Failed prop type: Invalid prop storeof typefunctionsupplied toProvider, expected object. in Provider

so changed it since dummyStore is returning an object literal:

it('contains expected actions', () => {
  const homePage = shallow(<HomePageContainer />, {
    wrappingComponent: Provider,
    wrappingComponentProps: { store: dummyStore() }
  });

  expect(homePage.props().fetchFeaturedCompanies).to.exist;
  expect(homePage.props().fetchCompanies).to.exist;
});

Error: Could not find "store" in the context of "Connect(HomePageContainer)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider>

stack trace

TypeError: store.getState is not a function
    at /Users/coffeegrinds/code/other/projects/we-do-tdd/node_modules/react-redux/lib/components/Provider.js:31:18
    at Object.useMemo (node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:539:23)
    at useMemo (node_modules/react/cjs/react.development.js:1521:21)
    at Provider (node_modules/react-redux/lib/components/Provider.js:30:42)
    at ReactShallowRenderer.render (node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:829:32)
    at renderElement (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:632:26)
    at fn (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:694:46)
    at withSetStateAllowed (node_modules/enzyme-adapter-utils/src/Utils.js:99:18)
    at Object.render (node_modules/enzyme-adapter-react-16/src/ReactSixteenAdapter.js:694:20)
    at WrappingComponentWrapper.ShallowWrapper (node_modules/enzyme/src/ShallowWrapper.js:411:22)
    at new WrappingComponentWrapper (node_modules/enzyme/src/ShallowWrapper.js:1771:40)
    at makeShallowOptions (node_modules/enzyme/src/ShallowWrapper.js:355:29)
    at new ShallowWrapper (node_modules/enzyme/src/ShallowWrapper.js:393:21)
    at Object.shallow (node_modules/enzyme/src/shallow.js:10:10)
    at Context.<anonymous> (src/test/unit/components/homepage/Home.spec.tsx:51:22)
    at processImmediate (internal/timers.js:456:21)

Screen Shot 2020-09-19 at 2 52 52 AM

now we're back to the same old issue. Sounds like it might be treating context as looking for a react hook context. I'm not using hooks. But it looks like the store we're passing to Provider seems to be getting to provider, just that connect isn't finding it in context still so it makes me think there's some issue with context with enzyme/connect/redux going on here.

trialspark/enzyme-context#33 (comment)

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

went back to ignoring TS and trying store as prop again:

it('contains expected actions', () => {
  // @ts-ignore
  const homePage = shallow(<HomePageContainer store={fakeStore(
    {
      country: {
      countriesReceived: true,
      countries: [countryStub]
    },
    companies: {
      companiesReceived: true,
      companies: [companyStub],
    }
  }
)}/>);

  expect(homePage.props().fetchFeaturedCompanies).to.exist;
  expect(homePage.props().fetchCompanies).to.exist;
});

AssertionError: expected undefined to exist

So for some reason .props() no longer finds fetchFeaturedCompanies or fetchCompanies and this test, same assertions (nothing changed) worked fine before upgrading to latest enzyme and Redux 7.

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

I also just verified with the Redux team that store as a prop on the component under test (no need for Provider) was re-introduced in React-Redux v7.

So that leaves us with a possible enzyme issue here when it can't find that prop anymore on the surface area of the shallowed component? Why would these tests that were passing fine with enzyme shallow before v7 of Redux no longer work?

You say it has nothing to do with TS but I added type PropsFromRedux = ConnectedProps<typeof connector> which is the incoming props to my Container which I did NOT have prior to refactoring that container to use TS. So why can't enzyme find the props here which should apparently be found through Component<PropsFromRedux> OR is there some other issue with enzyme here that's not working right that I'm finding?

@dschinkel
Copy link
Author

the fix: expect(homePage.childAt(0).props().fetchFeaturedCompanies).to.exist; and this is related to changes in react-redux how connect wraps stuff. I knew this wasn't just me.

@ljharb
Copy link
Member

ljharb commented Sep 19, 2020

enzyme - like all runtime javascript code - doesn't know about, see, or care about type notations. Adding anything in type-space should have no impact on value-space.

Glad you found a fix.

@dschinkel
Copy link
Author

dschinkel commented Sep 19, 2020

so the real problem was how you have to access things through Redux now via v6 or 7. I had to add childAt(0) now to my examples above.

One person thinks it's because:
That might have been a leaking implementation side effect from back when it was a class component, but it now wraps with a function component, not a class component

this person was talking about Redux and Connect. So the recent React-Redux/Connect upgrade did effect my tests here and enzyme no longer worked the way I had it because the SUT in this case connect which is part of it, changed. This is pretty fragile anyway but...just thought I'd put that in here. Testing through connect is always going to be like this when you test through third party HOCs like this, which is why it's important to just push your logic out of your Views and decouple or just not use stuff like Connect in the first place.

Also to get rid of the issue of TS complaining about store is not a recognized prop from tests, I added a test.d.ts file to my test folder with:

declare namespace JSX {
  interface IntrinsicAttributes {
    store: any;
  }
}

in package.json, then reference it:
"types": "src/test/test.d.ts",

@QasimRRizvi
Copy link

When I do this

wrapper = shallow(<Login />, {
   wrappingComponent: Provider,
   wrappingComponentProps: {store}
 });

It generates error

Could not find "store" in the context of "Connect(LoginBase)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(LoginBase) in connect options.

Any fix for this
Note: store is

store = mockStore({
   "authReducer": {
     isLoading: false,
   },
   login: () => {},
 });

@miteshdhaduk
Copy link

The same issue found. Not able to pass the store props in redux Provider.

Did anyone get any solution?

Versions:
react-redux: 7.2.2
next-redux-wrapper: 6.0.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants