JesperLekland / react-native-svg-charts

📈 One library to rule all charts for React Native 📊
MIT License
2.36k stars 410 forks source link

[Feature] Support mocking #418

Open hyochan opened 4 years ago

hyochan commented 4 years ago

image

Any idea how we can mock this module and make better testing coverage?

soulfresh commented 3 years ago

I'm not sure if this issue is specific to mocking or unit testing in general but I had a really hard time getting unit tests around these chart components. For anyone who may end up here in the future, here's what I had to do to make my chart components testable. Using the following steps allowed me to reach 100% code coverage under Jest.

1. Trigger react-native-svg-charts component onLayout handlers

react-native-svg-charts components listen for onLayout changes before they render any data. You will need to trigger these event handlers for each component in your tests. This is a utility function I wrote to make that easy with @testing-library/react-native.

function _findByProp(root: any, prop = '', found: any[] = []) {
  if (!root) return found

  if (root.props) {
    if (Object.keys(root.props).includes(prop)) found.push(root)
  }

  if (root.children && root.children.length) {
    root.children.forEach((c) => _findByProp(c, prop, found))
  }

  return found
}

/**
 * Find components in the given component hierarchy based
 * on the name of one of their props. For example,
 * `findByProp(root, 'foo')` will return a list of all
 * components with a `foo` prop of any value.
 */
export function findByProp(
  /**
   * The root of the component tree to search through.
   */
  root: any,
  /**
   * The name of the prop you are using to select components.
   */
  prop = '',
) {
  return _findByProp(root, prop)
}

/**
 * Fire the same layout event for all components that have
 * an `onLayout` prop. Generally you won't want to do this because
 * the layout dimensions would be different for each component.
 * However, this can be useful if you don't have a way to determine
 * which components should receive specific dimensions.
 */
export function fireLayoutEvent(
  /**
   * The root node to search for components with `onLayout` props.
   */
  root: any,
  /**
   * The event options inside of `event.nativeElement.layout`
   */
  options = {
    width: 0,
    height: 0,
  },
) {
  findByProp(root, 'onLayout').forEach((n) =>
    fireEvent(n, 'layout', { nativeEvent: { layout: options } }),
  )
}

Then in my test files:

beforeEach(() => {
  const screen = render(component, options)

  // Force a layout event.
  fireLayoutEvent(screen.container, {width: 100, height: 100})
})

2. Mocks for react-native-svg

Add a mock implementation of react-native-svg from here: https://gist.github.com/JCMais/8302a1646ccc9759237947a66bdda8e0

Adding these mocks makes it really simple to query the components, especially if you use @testing-library/react-native

3. Chart accessibility props

In order to query the chart elements, I was able to add accessibility attributes to the various chart components using the svg prop they all have.

In my component code:

<XAxis
  data={chartData}
  svg={{
    accessibilityRole: 'columnheader',
  }}
></XAxis>

Then in my tests I was able to do the following:

// Get all x axis labels using the column header role
screen.getAllByRole('columnheader')
// Get an x axis label using its text
screen.getByText('January')

I was able to follow the accessibility paradigm described in the following article: https://tink.uk/accessible-svg-line-graphs/. This approach only works on web and test. For native I just treat my component as an image with a very descriptive label:

<Container
  // On native, describe the chart as an image.
  accessible={Platform.OS === 'web' ? undefined : true}
  accessibilityLabel={Platform.OS === 'web' ? undefined : makeA11ySummary()}
  accessibilityRole={Platform.OS === 'web' ? undefined : 'image'}
>
  ...
</Container>

I would love to know if there are better/easier approaches out there.