enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

How to test Auth0 component using jest | enzyme #2325

Open jahnaviv opened 4 years ago

ljharb commented 4 years ago

You'll have to elaborate; what is Auth0? What is an Auth0 component?

jahnaviv commented 4 years ago

auth0 component:

import React from 'react';
import Auth0Lock from 'auth0-lock';
import jwtDecode from 'jwt-decode';
import PropTypes from 'prop-types';
import { setUser } from 'Utils/googleAnalytics';
import Storage from 'Utils/storage';
import './Auth0.scss';

const propTypes = {
  onLogout: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.oneOf([undefined]),
  ]),
  children: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.string,
    PropTypes.element,
    PropTypes.arrayOf(PropTypes.element),
    PropTypes.arrayOf(PropTypes.element),
    PropTypes.oneOf([undefined, false, null]),
  ]),
};

const defaultProps = {
  children: undefined,
  onLogout: undefined,
};

class Auth0 extends React.Component {
  constructor(props) {
    super(props);

    this.auth = new Auth0Lock(process.env.AUTH0_CLIENT_ID, process.env.AUTH0_DOMAIN, {
      configurationBaseUrl: 'https://cdn.auth0.com',
      auth: {
        responseType: 'token id_token',
        redirectUrl: `${window.location.href}`,
        redirect: false,
        audience: `${process.env.AUTH0_AUDIENCE}`,
        params: {
          scope: 'openid profile email roles read:brands read:users read:analytics',
        },
      },
      theme: {
        logo: 'https://s3.amazonaws.com/insights.clearstream.tv/assets/EMX+Logo+-+primary-red.png',
        primaryColor: '#c72127',
      },
      languageDictionary: {
        title: 'EMX Digital',
      },
      closable: false,
    });

    this.auth.on('authenticated', this.onAuthenticated);
    this.auth.hasRoles = this.hasRoles;
    this.auth.logout = this.logout;
    this.auth.isAuthenticated = this.isAuthenticated;

    this.state = { authenticated: false };
  }

  componentWillMount = () => {
    this.setState({ authenticated: this.isAuthenticated() });
  };

  onAuthenticated = (authResult) => {
    this.auth.hide();

    const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());

    Storage.set('access_token', authResult.accessToken);
    Storage.set('id_token', authResult.idToken);
    Storage.set('expires_at', expiresAt);
    Storage.set('user_info', authResult.idTokenPayload.name);

    setUser(authResult.idTokenPayload.name.replace('@', '.'));

    this.auth.getUserInfo(authResult.accessToken, (error, profile) => {
      if (error) return console.log(error);
      Storage.set('profile', JSON.stringify(profile));
      return this.setState({ authenticated: true });
    });
  };

  isAuthenticated = () => {
    let accessToken = Storage.get('access_token');
    accessToken = accessToken ? jwtDecode(accessToken) : {};

    let expiresAt = 0;
    if (accessToken) {
      expiresAt = accessToken.exp * 1000 || 0;
    }
    const now = new Date().getTime();
    const diff = expiresAt - now;
    if (diff > 0) {
      if (this.timer) {
        clearTimeout(this.timer);
        this.timer = undefined;
      }
      this.timer = setTimeout(() => this.logout(), diff);
    }

    const authenticated = now < expiresAt;

    if (authenticated) this.auth.hide();
    else this.auth.show();

    return authenticated;
  };

  hasRoles = (...args) => {
    const roles = jwtDecode(Storage.get('access_token'))['http://emxanswers.com/roles'] || [];
    for (let i = 0; i < roles.length; i += 1) {
      if (args.indexOf(roles[i]) > -1) return true;
    }
    return false;
  };

  logout = () => {
    const {
      onLogout,
    } = this.props;
    // Clear Access Token and ID Token from local storage
    Storage.remove('access_token');
    Storage.remove('id_token');
    Storage.remove('expires_at');
    Storage.remove('user_info');
    Storage.remove('profile');
    this.setState({ authenticated: false });
    this.auth.show();
    return onLogout && onLogout();
  };

  render = () => {
    const {
      authenticated,
    } = this.state;
    const {
      children,
    } = this.props;
    this.isAuthenticated();
    return typeof children === 'function'
      ? children({ auth: this.auth, isAuthenticated: authenticated })
      : children;
  };
}

Auth0.propTypes = propTypes;
Auth0.defaultProps = defaultProps;

export default Auth0;
ljharb commented 4 years ago

First, don't put functions in class fields.

Second, make propType warnings fail your tests by mocking console.error/console.warn.

Third, write as many tests as you can using shallow, asserting on what's rendered based on what props are passed to Auth0.

At that point, you should have very little uncovered; you can write a mount test for that.

jahnaviv commented 4 years ago
test('Should call hasRoles()', () => {
    wrapper.instance().onAuthenticated(authResult);
    wrapper.instance().hasRoles('RTA User');

  });

For this test-case its gives me error Invalid token specifiedInvalidTokenError and its pointed to import jwtDecode from 'jwt-decode'; line

ljharb commented 4 years ago

no, not like that.

I'm suggesting starting by mocking nothing, and passing different props in, and shallow-rendering it, and asserting on what it renders.