DEV Community

Radoslav Stankov
Radoslav Stankov

Posted on • Edited on • Originally published at blog.rstankov.com

Using "data-test" in Tests

When testing HTML components, I often see people using class names as selectors. For example:

element.find('.description button.expand-button').simulate('click');

While this seems convenient at first, there are some drawbacks. HTML structure and css classes tend to change due to design changes. Which will cause you re-write tests quite often. Also, if you are using css-modules you can't rely on class names.

Because of that, for quite some time now, I have started marking elements with data-test attribute.

React example (using enzyme and chai-enzyme):

describe(Description.name, () => {
  it('cut off text based on `cutoffLength`', () => {
    const el = shallow(<Description text="test" cutoffLength={1} />);

    expect(el).to.have.text('t...');
    expect(el).not.to.have.text('test');
  });

  it('hides expand button when text is short', () => {
    const el = shallow(<Description text="test" cutoffLength={10} />);
    expect(el).not.to.have.descendants('[data-text="expand-button"]');
  });

  it('shows expand button when text is long', () => {
    const el = shallow(<Description text="test" cutoffLength={1} />);
    expect(el).to.have.descendants('[data-test="expand-button"]');
  });

  it('clicking expand button reveals the whole text', () => {
    const el = shallow(<Description text="test" cutoffLength={1} />);

    el.find('[data-test="expand-button"]').simulate('click');

    expect(el).not.to.have.descendants('[data-test="expand-button"]');
    expect(el).to.have.text('test');
  });
});

The component code:

import React from 'react';
import styles from "./style.css";

export default Description extends React.Component {
  state = { expanded: false };

  render() {
    const { text, cutoffLength } = this.props;

    if (this.state.expanded || text.length < cutoffLength) {
      return (
        <div className={styles.description}>
          {this.props.text}
        </div>
      );
    }

    return (
      <div className={styles.description}>
        {`${ text.substr(0, cutoffLength) }...`}
        <button 
          data-test="expand-button" 
          className={styles.expand} 
          onClick={this.expand}>show more</button>
      </div>
    );
  }

  expand = () => {
    this.setState({ expanded: true });
  };
}

I'm also using data-test attributes for testing with Capybara in Ruby land.

describe 'Product page' do
  it 'has product description rev' do
    product = create :post, :with_long_description

    visit product_path(product)

    expect(page).not_to have_text product.description

    # This can be extracted into `find_test_attr` or `click_test_attr`
    find(:css, '[data-test="expand"]').click

    expect(page).to have_text product.description

    # This can be extracted into `have_test_arr`
    expect(page).not_to have_css('[data-test="expand"]')
  end
end

Top comments (2)

Collapse
 
mikerogers0 profile image
Mike Rogers ✈️

I done this before, and it's really good way of targeting elements. That said, instead of data-test I just copied bootstraps approach to attributes (e.g. data-toggle="dropdown" & data-tatget="#something"), which seemed a bit more semantic.

Collapse
 
rstankov profile image
Radoslav Stankov

I like the bootstrap technique too. Used it in couple of times in the past. Don't do it recently, since most of browser work, I do is React now days.

What I like about data-test is the intent it reveals. This is for easier test targeting. Not implementation detail.