Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Testing

How to create custom linting rules in React Testing Library

Dive into the creation of custom linting rules with our step-by-step guide. Enhance your React projects with tailored best practices.
Dale Karp
Josh Justice
|
July 30, 2023
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Have you ever wondered where linting rules come from or how they’re written? Recently a couple of Double Agents (Dale Karp and Josh Justice) found the need for a new linting rule, got together to implement said rule, and contributed it to a linting library so everyone could benefit.

We thought breaking that process down could also be useful. In this post, Dale discusses why linting is valuable, takes a look at this new lint rule, and walks through the impetus behind its creation. Then, in a follow-up post, Josh will do a deep dive into the process of pairing on creating the rule and getting it published in an open-source library.

Why linters are an important tool for developers

A linter provides many benefits. It can guide developers toward best practices or point out issues with specific code patterns while recommending alternative implementations. A linter can be used to enforce specific rules within a code base to maintain a consistent style across different files and authors.

When a linter is integrated into an IDE or code editor, it can feel like pair programming with a very knowledgeable developer - the kind that has a deep understanding of the most obscure edge cases. This allows us to free up space in our minds to focus on solving the actual problems at hand instead of fretting over smaller details like formatting or using the correct language constructs for the job.

Linters can be useful in reminding developers about best practices if they haven’t worked in a particular language for an extended period of time. Linters can also be great teaching tools while exploring a new language or framework. For instance, Clippy — the primary linting tool for the Rust language — goes beyond pointing out not-so-obvious problems. It also offers alternatives to existing code — using your own code as an example in order to write Rust in a more idiomatic way.

React Testing Library and queries

React Testing Library (or RTL) is a popular tool used to help write tests for React components. The library supplies convenience functions specific to React for rendering components and querying & interacting with DOM nodes.

When paired with a test runner such as Jest, developers can chain together RTL queries with different “matchers” — functions that test for a specific thing to be true or false. This allows a developer to write easy-to-read and succinct tests. Matchers are provided by both Jest and RTL and operate on the result of an expect() call in Jest — the expectation object.

For example, given the following statement:

expect(screen.getByRole("dialog")).toBeVisible();

We use the query function getByRole() to retrieve a specific element from the DOM tree. The result of this query is given to Jest’s expect(), which returns an expectation object. We can then run matchers such as toBeVisible() in order to assert that our DOM node should be visible to users.

With RTL, there are three “families” of query functions one can use. The first are the get* queries. The get* queries are used to look for elements that are assumed to exist on the page. If a get* query fails to return the expected number of results, an error is thrown by the query function itself.

The next type are the query* queries. While saying “query queries!” can be quite the mouthful, they are simple to understand. query* queries work similarly to get* except the function will not throw an error if the desired DOM node is not found; instead, null is returned. This is useful for tests where we want to assert that a specific DOM node will not exist within the tree.

Finally, we have the find* queries. These are similar to get* but use the Promise API to wait for elements that appear asynchronously. The promise will be rejected with an error if the desired DOM node is not found. We won’t be discussing these queries in this post, but it’s good to know that they exist.

Let’s do a quick recap of everything discussed so far: We use a query from RTL to search for a DOM node in our component. The result of this query is passed to Jest’s expect() function so we can use matchers to assert that some property of our component is true or false.

Now that we have an understanding of how both RTL and Jest queries work, it’s time for a story about how one linting rule could have saved me some time and a headache after an extended absence from using React and RTL.

The story so far…

After months of working on migrating a legacy backend codebase at a client, I was finally back to writing React! The work in question involved contributing to a client’s new component library.

While writing tests for the component I was building, I wrote an assertion:

expect(screen.queryByLabelText("Close")).toBeVisible();

On the surface, there seems to be nothing wrong with this query. We want to make sure that a DOM element with the label text “Close” is visible to a user. Easy, right?

Unfortunately, the semantics of the statement are not quite correct. As discussed above, query* will return the element if it exists in the DOM tree or null if no match is found. As the author of the test, I know that this element will exist in the tree when making this assertion. But will that be clear to others who may look at these tests in the future?

The statement written above reads as “An element with a label of ‘Close’ might exist, and if it does, it should be visible.” But if we were to change the query in our example to getByLabelText, the statement would read, “An element with a label of ‘Close’ will exist in the tree, and it will be visible!” While the difference might seem subtle, using the correct functions for tests can communicate intent more clearly. It can also lead to error messages that more accurately describe what went wrong. If I stuck with queryByLabelText and, for some odd reason, the element was not in the DOM, the following error would be raised from the matcher:

received value must be an HTMLElement or an SVGElement. Received has value: null

After thinking about it for a bit, I’d eventually figure out what went wrong, but it wouldn’t be obvious from the get-go. The error message raised by getByLabelText would describe the problem clearly:

TestingLibraryElementError: Unable to find a label with the text of: Close

The error message is now thrown by the call to getByLabelText. Succinct error messages lead to less time spent scratching our heads and allow us to quickly fix the issue and move on.

Creating a rule

I wondered to myself why a linter hadn’t caught this particular error. It seemed like a common mistake anyone could make — especially when said person hadn’t written any React in a good few months! There is a plugin for the popular JavaScript linter eslint, called eslint-plugin-testing-library, that adds linting rules covering best practices for writing tests with RTL. I would have thought that the rules included with the plugin would have caught this particular mismatch of query and matcher, but it did not.

Fate must have dictated that I encounter this particular issue. Around the same time, my colleague Josh Justice encountered the exact same issue! He put a call out in our company Slack to pair with another Double Agent to fix this, and I couldn’t help but respond.

The fruit of our labour was prefer-query-matchers — a new configurable rule in eslint-plugin-testing-library. This rule Josh and I created allows for the configuration of matchers to be restricted for use with a specific type of query.

For example, if I want my linter to have my back and kindly remind me that what I did above might not be completely clear to others, I could configure a rule to restrict the toBeVisible matcher to usage with get* queries:

rules: {
  ...,
  'testing-library/prefer-query-matchers': [
    'error',
    {
      validEntries: [{ matcher: 'toBeVisible', query: 'get' }],
    },
  ],
},

Now my editor will kindly remind me to use the correct query, saving me time and future headaches!

VSCode reporting eslint errors in the editor using the above example scenario

Using the linting rule

If you’ve read this post and thought to yourself, “Wow, I sure could use this linting rule in my own project!” here’s how to enable it:

  1.  Install eslint and eslint-plugin-testing-library. eslint-plugin-testing-library should be at least v5.11.0 in order to have access to the prefer-query-matchers rule.
  2.  In your project’s eslint configuration file, enable the rule by adding the following key/value to the rules object:
'testing-library/prefer-query-matchers': [
  'error',
  {
    validEntries: [{ matcher: 'toBeVisible', query: 'get' }],
  },
],

And that’s it! With the rule enabled, you too can have one less thing to keep in your working memory, knowing that eslint and prefer-query-matchers have your back.

Now you have everything you need to put the prefer-query-matchers rule into use. If you’d like to know more about the process of writing it, in the next post, Josh Justice will talk about his own encounter with using the wrong query for a matcher, putting out the call to pair on creating the rule, and our experience of contributing to an open source project.

Join the conversation about this post on our N.E.A.T community

Not a N.E.A.T. community member yet? More info.

Related Insights

🔗
A guide to building a custom ESLint rule for React Testing Library

Explore our insights

See all insights
Leadership
Leadership
Leadership
Why we coach the system, not just the team

Slow delivery isn’t usually about your people—it’s about your system. Shifting focus to incremental improvements in the system helps change not just processes but behaviors for lasting change.

by
Doc Norton
Developers
Developers
Developers
Developer QA checklist for feature releases

Quality Assurance is a mindset integrated throughout development to catch issues early, build user trust, and reduce maintenance costs. These recommended procedures for dev teams without dedicated QA roles establish collective responsibility for ensuring feature stability, functionality, and usability before release.

by
Lee Quarella
Developers
Developers
Developers
From engineer to consultant: The powerful shift from inward to outward focus

What transforms a skilled software engineer into an exceptional consultant? Approach new codebases with respect rather than judgment, embrace constraints as creative boundaries, and prioritize client needs over personal preferences.

by
Dave Mosher
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
No items found.
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.