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

Provide testing/mocking instructions and export react-tracking/mock #134

Open
tizmagik opened this issue Aug 19, 2019 · 7 comments
Open
Labels

Comments

@tizmagik
Copy link
Collaborator

tizmagik commented Aug 19, 2019

I've been meaning to provide this as importable within react-tracking via something like: import track, { mockTrackEvent, TrackingPropType } from 'react-tracking/mock'; but haven't had a chance yet. PRs welcome! 😁

/* __mocks__/react-tracking.js */

const PropTypes = require('prop-types');

const mockTrackEvent = jest.fn();

module.exports.TrackingPropType = PropTypes.shape();

module.exports = () => (...clsOrMethod) => {
  if (clsOrMethod.length === 1) {
    // decorating a class
    const [cls] = clsOrMethod;
    cls.defaultProps = {
      ...cls.defaultProps,
      tracking: {
        trackEvent: mockTrackEvent,
      },
    };
    return cls;
  }

  // decorating a method
  return clsOrMethod[1].initializer;
};

module.exports.mockTrackEvent = mockTrackEvent;

Originally posted by @tizmagik in #112 (comment)

@marcelosedano
Copy link

marcelosedano commented Apr 10, 2020

Hi team,

We've been integrating this wonderful package into our own application but have faced some issues with fixing broken unit tests as a result. We tried using the mock above to set the tracking default prop for our class components but it didn't work. I'm almost certain that the issue is that the defaultProps for our components are defined outside of the class definition, a common pattern for React developers.

Here's an example:

@track({ module: 'MyComponent' })
export class MyComponent extends Component {
  render() {
    ...
  }
}

MyComponent.propTypes = {
  tracking: TrackingPropType,
};

MyComponent.defaultProps = {
  tracking: null,
};

export default connect(mapStateToProps)(MyComponent);

When we move the defaultProps (and propTypes) definitions inside of the class definition, it works. For now we're just passing in a mock tracking prop ourselves in our tests which works however we're still getting this warning for some reason:
prop type 'tracking' is invalid; it must be a function, usually from the 'prop-types' package, but received 'undefined'.

EDIT: the prop-type warning was due to the mock using module.exports and us importing with import from.

@tizmagik
Copy link
Collaborator Author

Hmm it may be because of the connect() wrapper? Could you try something like this?:

@track({ module: 'MyComponent' })
export class MyComponent extends Component {
  render() {
    ...
  }
}

const ConnectedMyComponent = connect(mapStateToProps)(MyComponent);

ConnectedMyComponent.propTypes = {
  tracking: TrackingPropType,
};

ConnectedMyComponent.defaultProps = {
  tracking: null,
};

export default ConnectedMyComponent;

If not, maybe you could create a repro in Codesandbox or something? I can try and take a closer look when I get a chance.

@l225li
Copy link

l225li commented Aug 7, 2020

Hi team,

We've been integrating this wonderful package into our own application but have faced some issues with fixing broken unit tests as a result. We tried using the mock above to set the tracking default prop for our class components but it didn't work. I'm almost certain that the issue is that the defaultProps for our components are defined outside of the class definition, a common pattern for React developers.

Here's an example:

@track({ module: 'MyComponent' })
export class MyComponent extends Component {
  render() {
    ...
  }
}

MyComponent.propTypes = {
  tracking: TrackingPropType,
};

MyComponent.defaultProps = {
  tracking: null,
};

export default connect(mapStateToProps)(MyComponent);

When we move the defaultProps (and propTypes) definitions inside of the class definition, it works. For now we're just passing in a mock tracking prop ourselves in our tests which works however we're still getting this warning for some reason:
prop type 'tracking' is invalid; it must be a function, usually from the 'prop-types' package, but received 'undefined'.

EDIT: the prop-type warning was due to the mock using module.exports and us importing with import from.

Hi, could you elaborate on the EDIT? What did you do to fix it? I'm newbie to react, just got a task to fix this problem. I also got a TypeError bug: TypeError: (0 , _tracking.track) is not a function.

Thanks a lot in advance!

@tizmagik
Copy link
Collaborator Author

Hey @l225li are you still having this issue? Could you share more of your code or maybe create a Codesandbox example so I can take a closer look?

@wokkaflokka
Copy link

wokkaflokka commented Aug 19, 2020

@tizmagik cc @l225li

With respect to the comment from @l225li, I also observed this issue recently when integrating react-tracking. In my case, our project is a typescript project using stateless functional components and ES module imports, and using jest for testing.

For tracking implemented at the component level, a lot of the code looks like this...

import track from 'react-tracking'

const MyComponent = () => { ... }

export default track({
  event: 'my-component.presented'
})(MyComponent)

The key for mocking, then, is to mock the default exported function (track).

By default, jest mocks do not support ES module semantics; when react-tracking was not mocked for ES module syntax, I observed variants of the error described by @l225li.

 FAIL  src/components/pages/Foo/Bar/Baz/__tests__/Baz.test.tsx
  ● Test suite failed to run

    TypeError: (0 , _reactTracking.default)(...) is not a function

      120 | }
      121 | 
    > 122 | export default track({
          |                ^
      123 |   event: 'foo-bar.baz.presented',
      124 | })(Baz)
      125 | 

      at Object.<anonymous> (src/components/pages/Foo/Bar/Baz/Baz.tsx:123:12)
      at Object.<anonymous> (src/components/pages/Foo/Bar/Baz/__tests__/Baz.test.tsx:10:1)

This prevented me from mocking react-tracking using a manual mock; however, I was able to get a satisfactory mocking solution using the following configuration.

jest.mock('react-tracking', () => {
    const trackEvent = jest.fn()
    return {
        __esModule: true,
        default: jest.fn(() => (id) => id),
        useTracking: jest.fn(() => ({
            trackEvent,
        })),
    }
})

Hope this helps.

Aside: really love the work NYT team has done with this library. In recent years I have spent a lot of time working on Android (where NYT team also shines), and was very pleasantly surprised to find this library, which shares a core philosophy and solves foundational problems I encountered in past projects in the mobile space. Great job.

@tizmagik
Copy link
Collaborator Author

Thank you @wokkaflokka , that's very helpful. And thank you for the kind words! 🤗

@huguesbr
Copy link

huguesbr commented Nov 3, 2020

To disable react-tracking in test, we've used something like this:

// lib/tracking/index.js
import tracking, { useTracking } from 'react-tracking';

let exportedTracking = tracking
let exportedUseTracking = useTracking

if (process.env.NODE_ENV == 'test') {
  const Identity = (Component) => (Component)
  exportedTracking = () => (Identity)
  exportedUseTracking = () => {
    trackEvent: () => {},
    getTrackingData: () => ({})
  }
}

export { exportedTracking as tracking, exportedUseTracking as useTracking }
import { tracking, useTracking } from 'lib/tracking'

It doesn't solve testability, just avoid dealing with renamed HOC classes and such in test.

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

No branches or pull requests

5 participants