DEV Community

Radoslav Stankov
Radoslav Stankov

Posted on • Updated 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.

An Animated Guide to Node.js Event Loop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.