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)
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.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.