reactjscypressreact-typescriptcypress-component-test-runner

How do I use data-testid attribute with a React Component Test?


I am doing a Component Test on a simple react component, which I want to render and then do some tests on.

The problem is that cy.get() does not seem to find the component based on a data-testid attribute.

I tested it with a simple div and the div can be found just fine.

I also noticed when inspecting the DOM in the cypress runner, that the Component is not being rendered, but the contents of it are. Basically there is no <MockComponent>

  it.only("renders and finds Component", () => {
    cy.mount(
      <ApolloProvider client={client}>
        <MockComponent data-testid='mock-component' {...someData} />
        <div data-testid='my-div'></div>
      </ApolloProvider>
    );

    cy.get("[data-testid='my-div']").should('exist'); //this works
    cy.get("[data-testid='mock-component']").should('exist'); //this doesn't

  });

How can I get the <MockComponent> in cypress, so that I can run tests on it?


Solution

  • You can't add data-id attributes directly to the the React component and have it appear on the DOM content.

    All attributes (data or otherwise) that you put on MockComponent are passed in as props to your component. This is standard for React.

    So, if you examine props inside MockComponent, it will have a property named data-testid with a value of mock-component.

    You will have to convert that into an attribute inside MockComponent, on one of it's rendered elements.

    Note that MockComponent never appear in the DOM, just it's rendered children.

    For example, explicitly:

    const MockComponent = (props) => {
      return (
        <div data-testid={props["data-testid"]}>    
          <span>this is mock component</span>
        </div>
      )
    }
    
    ...
    
    cy.get("[data-testid='mock-component']")   // passes
    

    The <div> inside MockComponent's return value is the "outer" element rendered to DOM, so that's where you need the data-id.


    Or you can do it implicitly. Since someData is part of your component API, you can split it off and assume the remainder will be utilized as attributes (without actually knowing what they are).

    const MockComponent = (props) => {
      const {someData, ...rest} = props;
    
      const [data, setData] = useState(someData);
    
      return (
        <div {...rest}>
          <span>this is mock component</span>
        </div>
      )
    }
    
    ...
    
    cy.get("[data-testid='mock-component']")   // passes
    

    One more way is to add a child to MockComponent, which must be a real element.

    All child elements are passed as props.children, and you must render them inside the return value.

    const MockComponent = (props) => {
      const {someData} = props
    
      const [data, setData] = useState(someData);
    
      return (
        <div>
          <span>this is mock component</span>
          {props.children}
        </div>
      )
    }
    
    ...
    
    cy.mount(
      <MockComponent {...someData} >
        <span data-testid='mock-component-child' ></span>
      </MockComponent>
    );
    
    cy.get("[data-testid='mock-component-child']")   // passes
      .parent()                              // outer element of MockComponent