reactjsreact-testing-libraryreact-testing

How to mock a parent component which passes props to child component using react testing library


**How to check for the dynamic state changes in a parent component and write the test case using the react testing library if the props passed to the child component are based on the state changes which are happening dynamically in the parent component. Can someone help me with this?enter image description here

App.js

    import React, { Component } from 'react';
    import './App.css';
    import TextArea from './components/TextArea/TextArea';
    
    class App extends Component {
      constructor() {
        super();
        this.state = {
          textAreaParams: {},
        };
      }
      componentDidMount() {
        this.setDefaultTextAreaMessage();
      }
      setDefaultTextAreaMessage = () => {
        this.setState({
          textAreaParams: { message: 'Hello' },
        });
      };
      render() {
        const { textAreaParams } = this.state;
        return (
          <div className="App">
            {Object.keys(textAreaParams).length > 0 ? (
              <TextArea params={textAreaParams} />
            ) : null}
          </div>
        );
      }
    }
    
    export default App;

TextArea.js

    import { Component } from 'react';
    
    class TextArea extends Component {
      constructor(props) {
        super(props);
        this.state = {
          message: this.props.params.message,
        };
      }
      render() {
        return (
          <div>
            <textarea
              rows="4"
              cols="50"
              value={this.state.message ? this.state.message : ''}
              placeholder="test"
              onChange={() => {}}
            >
              {this.state.message}
            </textarea>
          </div>
        );
      }
    }
    
    export default TextArea;
   

App.test.js

    import App from './App';
    import { cleanup, render } from '@testing-library/react';
    
    describe('Rendering the App component and passing props to text area', () => {
      afterEach(cleanup);
      it('render the App component and check for the TextArea component', async () => {
        const props = { textAreaParams: { message: 'save' } };
        const { getByPlaceholderText } = render(<App {...props} />);
        const textAreaParams = getByPlaceholderText('test');
        expect(textAreaParams).toHaveTextContent('save');
      });
    });

Solution

  • We need to pass onChange handler prop from the App component to TextArea and then TextArea will component will call that handler when there is a change in the text area.

    updateTextAreaMessage = (messageInTextArea) => {
      this.setState({
        textAreaParams: { message: messageInTextArea}
      })
    }
    

    In the above code, messageInTextArea is a string value when we change the text in TextArea and when updateTextAreaMessage is called in the TextArea component with the same string value as a parameter, it will update the state in the App component.

    Full Implementation:

    App.js:

    import React, { Component } from "react";
    import './App.css';
    import TextArea from './components/TextArea/TextArea';
    
    class Main extends Component {
      constructor() {
        super();
        this.state = {
          textAreaParams: { message: "hello" } // we can provide default value here
        };
      }
    
      updateTextAreaMessage = (messageInTextArea) => {
        this.setState({
          textAreaParams: { message: messageInTextArea }
        });
      };
    
      render() {
        const { textAreaParams } = this.state;
        return (
          <div className="App">
            {Object.keys(textAreaParams).length > 0 ? (
              <TextArea
                params={textAreaParams}
                onUpdate={this.updateTextAreaMessage}
              />
            ) : null}
    
            <p aria-label="text area message">{textAreaParams.message}</p>
          </div>
        );
      }
    }
    
    export default Main;
    
    

    TextArea.js:

    import { Component } from "react";
    
    class TextArea extends Component {
      render() {
        return (
          <div>
            <textarea
              rows="4"
              cols="50"
              value={this.props.params.message ? this.props.params.message : ""}
              placeholder="test"
              onChange={(event) => this.props.onUpdate(event.target.value)}
            >
              {this.props.params.message}
            </textarea>
          </div>
        );
      }
    }
    
    export default TextArea;
    

    Now, we'll add the test for App.js. But the question is what to test here? The answer would we'll add the test for whether the state is updated or not when there is a change in the text of the TextArea component.
    import { render } from "@testing-library/react";
    import App from "./App";
    import TextArea from './components/TextArea/TextArea';
    
    describe("Rendering the App component and passing props to text area", () => {
      it("should render the App component with default message in TextArea", () => {
        const { getByPlaceholderText } = render(<Main />);
        const textAreaParams = getByPlaceholderText("test");
        expect(textAreaParams).toHaveTextContent(/hello/i);
      });
    
      it("should update the text area when we type something", () => {
        const { getByPlaceholderText, getByLabelText } = render(<Main />);
        userEvent.type(getByPlaceholderText("test"), "Anything");
        expect(getByLabelText(/text area message/i)).toHaveTextContent(/anything/i);
      });
    });
    
    describe("Rendering the Text Area component", () => {
      it("should render the TextArea component and calls onChange handlers when we type something", () => {
        const mockOnChangeHandler = jest.fn();
        const mockParams = { message: "save" };
        const { getByPlaceholderText } = render(
          <TextArea params={mockParams} onUpdate={mockOnChangeHandler} />
        );
        const inputTextArea = getByPlaceholderText("test");
        expect(inputTextArea).toHaveTextContent(/save/i);
    
        userEvent.type(inputTextArea, "Anything");
        expect(mockOnChangeHandler).toHaveBeenCalled();
      });
    });