Home > Net >  How to unit test a higher order component using jest and enzyme in react
How to unit test a higher order component using jest and enzyme in react

Time:07-24

I am currently trying to write a test to test what is inside of a higher order components

my test like so:

  let Component = withEverything(Header);
  let wrapper;
  it('renders correctly', async () => {
    wrapper = await mountWithSleep(
      <Component componentProps={{ session: { id: '2' } }} />,
      0.25
    );

    console.log(wrapper.debug());
  });
});

outputs the following:

<Component>
   <WithSession component={[Function: GlobalNav]} innerProps={{...}} />
</Component>

My with session file looks like the following:

import React, { Component, ComponentType } from 'react';
import { Session, session } from '@efa/web/src/modules/auth/authService';

import { Omit } from '@everlutionsk/helpers';
import { Subscription } from 'rxjs';

class WithSession extends Component<Props, State> {
  state: State = {
    session: undefined
  };

  private subscription: Subscription;

  componentDidMount() {
    this.subscription = session.subscribe(session => {
      this.setState({ session });
    });
  }

  componentWillUnmount() {
    this.subscription.unsubscribe();
  }

  render() {
    if (this.state.session === undefined) return null;

    const Component = this.props.component;
    const props = { ...this.props.innerProps, session: this.state.session };
    return <Component {...props} />;
  }
}

/**
 * Injects a current session to the given [component].
 */
export function withSession<P extends SessionProps>(
  component: ComponentType<P>
): ComponentType<Omit<P, keyof SessionProps>> {
  return props => <WithSession component={component} innerProps={props} />;
}

export interface SessionProps {
  readonly session: Session | null;
}

interface Props {
  readonly component: ComponentType;
  readonly innerProps: any;
}

interface State {
  readonly session: Session | null | undefined;
}

I have tried to do a jest.mock which gets me part of the way using this:

jest.mock('@efa/web/src/modules/auth/components/withSession', () => {
  //@ts-ignore
  const original = jest.requireActual(
    '@efa/web/src/modules/auth/components/withSession'
  );
  return {
    __esModule: true,
    ...original,
    withSession: component => {
      return component;
    }
  };
});

Using this module i can at least see a returned component instead of with session. But now the issue is i need to be able to set the session state. wondering if anyone can help?!

It is worth noting this is a project which we inherited i would not of implemented it this way ever!

CodePudding user response:

You're correct that this type of code absolutely makes testing more difficult. The "best" way in this case its probably a bit of work because the nicest way to go about it would be to switch this thing to context; and then add options to your test framework mount method to populate that context how you want from individual tests. Though, you can reach something similar with old fashioned HOCs.

There's a cheaper way, and that would be to allow options to be passed to withEverything (if this is used by the app as well, you can create a mirror one called withTestHocs or similiar. I.e.

withTestHocs(Header, {
    session: //... object here
})

Internally, this HOC would no longer call withSession whatsoever. Instead, it would call a HOC who's only purpose is to inject that session config object into the component for test reasons.

There's no reason to do complex mocking to get the session right on every test, its a waste of time. You only need that if you're actually testing withSession itself. Here you should be prioritising your test framework API that makes having custom session per test nice and simple. jest.mock is not easily parametrised, so that in itself is also another good reason to not go down that road. Again, the exception is when you're unit testing the actual session hoc but those tests are typically quite edge-casey and wont be using the core "test framework HOC" you'll use for all your userland/feature code -- which is what I'm focusing on here.

Note with this solution, you wouldn't need the complex jest mocking anymore (provided all tests were moved to the new way).

export const withEverything =
  (Component, {session}) =>
  ({ providerProps, componentProps }) =>
    (
      <MockedProvider {...providerProps}>
        <BrowserRouter>
          <FlashMessage>
            <Component {...componentProps} session={session} />
          </FlashMessage>
        </BrowserRouter>
      </MockedProvider>
    );

Now in your test

  it('renders correctly', async () => {
    wrapper = await mountWithSleep(
      const withEverything(Header, { session: { id: '2' }}),
      0.25
    );

    console.log(wrapper.debug());
  });

If you need to be able to manipulate the session mid-test, you could do that by returning a method from withEverthing that allows the session to be set, but im not sure if you need it.

  • Related