Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test styled Material-UI components wrapped in withStyles using react-testing-library?

I am trying to create a test with a styled Material-UI component using react-testing-library in typescript. I'm finding it difficult to access the internal functions of the component to mock and assert.

Form.tsx

export const styles = ({ palette, spacing }: Theme) => createStyles({
    root: {
        flexGrow: 1,
    },
    paper: {
        padding: spacing.unit * 2,
        margin: spacing.unit * 2,
        textAlign: 'center',
        color: palette.text.secondary,
    },
    button: {
        margin: spacing.unit * 2,
    }
});

interface Props extends WithStyles<typeof styles> { };

export class ExampleForm extends Component<Props, State> {
  async handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    // Handle form Submit
    ...
    if (errors) {
            window.alert('Some Error occurred');
            return;
        }
  }
  // render the form
}
export default withStyles(styles)(ExampleForm);

Test.tsx

import FormWithStyles from './Form';

it('alerts on submit click', async () => {
  jest.spyOn(window,'alert').mockImplementation(()=>{});
  const spy = jest.spyOn(ActivityCreateStyles,'handleSubmit');
  const { getByText, getByTestId } = render(<FormWithStyles />)
  fireEvent.click(getByText('Submit'));

  expect(spy).toHaveBeenCalledTimes(1);
  expect(window.alert).toHaveBeenCalledTimes(1);
})

jest.spyOn throws the following error Argument of type '"handleSubmit"' is not assignable to parameter of type 'never'.ts(2345) probably because ExampleForm in wrapped in withStyles.

I also tried directly importing the ExampleForm component and manually assigning the styles, was couldn't do so:

import {ExampleForm, styles} from './Form';

it('alerts on submit click', async () => {
  ...

  const { getByText, getByTestId } = render(<ActivityCreateForm classes={styles({palette,spacing})} />)

  ...
}

Got the following error: Type '{ palette: any; spacing: any; }' is missing the following properties from type 'Theme': shape, breakpoints, direction, mixins, and 4 more.ts(2345)

I'm finding it difficult to write basic tests in Typescript for Material-UI components with react-testing-library & Jest due to strong typings and wrapped components. Please Guide.

like image 462
Mayur Dhurpate Avatar asked May 01 '19 15:05

Mayur Dhurpate


3 Answers

First of all when you use render method of react-testing-library you don't need to worry about using withStyles or any wrapper because at the end it renders the component as it could be in the real dom so you can write your tests normally.

Then as far as I can see you are doing the same thing I did when I was starting with tests (it means you are going to become good at it ;). You are trying to mock an internal method and that is not the best approach to follow because what you need to do is to test the real method.

So let's image that we have a Register users component.

src/Register.tsx

import ... more cool things
import * as api from './api';

const Register = () => {
  const [name, setName] = useState('');
  const handleNameChange = (event) => {
    setName(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    if (name) {
      api.registerUser({ name });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <TextField
        id='name'
        name='name'
        label='Name'
        fullWidth
        value={name}
        onChange={handleNameChange}
      />
      <Button data-testid='button' fullWidth type='submit' variant='contained'>
        Save
      </Button>
    </form>
  );
}

The component is pretty simple, its a form with an input and a button. We are using react hooks to change the input value and based on that we call or not api.registerUser when handleSubmit event is fired.

To test the component the first thing we need to do is to mock api.registerUser method.

src/__tests__/Register.tsx

import * as api from '../api'

jest.mock('../api')

api.registerUser = jest.fn()

This will allow us to see if that method is called or not.

The next thing to do is ... write the tests, in this scenario we can test two things to see if handleSubmit is working correctly.

  1. Not to call api.registerUser if name is empty.
it('should not call api registerUser method', () => {
  const { getByTestId } = render(<Register />)
  fireEvent.click(getByTestId('button'))
  expect(api.registerUser).toHaveBeenCalledTimes(0)
})
  1. Call api.registerUser if name is not empty.
it('should call api registerUser method', () => {
  const { getByLabelText, getByTestId } = render(<Register />)

  fireEvent.change(getByLabelText('Name'), { target: { value: 'Steve Jobs' }})

  fireEvent.click(getByTestId('button'))
  expect(api.registerUser).toHaveBeenCalledTimes(1)
})

In this last test implicitly we are testing handleNameChange too because we are changing the name :) so name is not going to be empty and registerUser is going to be called.

The example with withStyles and typescript is in this repo.
The demo is here.

like image 144
Arnold Gandarillas Avatar answered Oct 26 '22 17:10

Arnold Gandarillas


Why don't you use enzyme with Full DOM Rendering? You can use simulate method to simulate events on mounted components.

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    const { count } = this.state;
    return (
      <div>
        <div className={`clicks-${count}`}>
          {count} clicks
        </div>
        <a href="url" onClick={() => { this.setState({ count: count + 1 }); }}>
          Increment
        </a>
      </div>
    );
  }
}

const wrapper = mount(<Foo />);

expect(wrapper.find('.clicks-0').length).to.equal(1);
wrapper.find('a').simulate('click');
expect(wrapper.find('.clicks-1').length).to.equal(1);
like image 43
Orkhan Huseynli Avatar answered Oct 26 '22 18:10

Orkhan Huseynli


You can use unwrap to unwrap the wrapped styled component then test it

import { unwrap } from '@material-ui/core/test-utils';
import {ExampleForm, styles} from './Form';

it('alerts on submit click', async () => {
  ...
const unwrapped = unwrap(ExampleForm);
    ...
}

Then you can do he required testing on the unwrapped object

like image 38
dushansilva Avatar answered Oct 26 '22 17:10

dushansilva