-
Notifications
You must be signed in to change notification settings - Fork 470
Separation of concerns in getByX / queryByX / etc. API #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks for the super detailed post! Maybe I’m missing something, but I’m not sure I see how this simplifies the API for the majority use case. I think the stance of the team is that the built-in queries are generally all that you need and it’s very easy to start testing implementation details if you go too far past them. The somewhat cumbersome nature of implementing custom queries, in my mind, is intentional. But all of that aside, I have a question in follow up. Thinking about it from the perspective of the folks who only use those the built in queries, do you think maybe this proposal actually potentially complicates the API? |
I'd say it would still be a simplification. Maybe it needs a few more imports, but conceptually it becomes easier to see what's going on. A good example is Current API: findByTitle(element, 'foo', {}, { timeout: 1000 }) What's that Proposed API: findBy(element, title('foo'), { timeout: 1000 }) No magic parameters, and it's obvious where to put options for title matching if I want them vs. where to put options for the finding. (I listed this in my original post although I got This simplification also manifests in typed languages. It would be very easy to define strict types for the proposed functions which will always be present (one of the advantages of users explicitly importing any query which they use), whereas the current API will fail to infer types automatically when binding. |
Also I understand the reluctance to make it too easy to write custom queries, but this is something which can be very important in tests. A few I can think of right now: // look up table cell by column header and row number
getBy(tableCell('my column header', 7))
// find the currently focused input element
getBy(isFocused())
// find all underlined text (maybe checks `getComputedStyle` to see if each element resolves as underlined)
getAllBy(underlined()) Just because it could be abused doesn't mean it should be hard to do! Hacks will find a way anyway. Might as well optimise for users who are trying to follow the suggested guidelines. |
I'm mostly unavailable for the next few days so I can't really look at this carefully until I'm back. But my first impression is that there's not enough of a benefit to justify making this change. Even though it would technically be backwards compatible, it would also enable multiple ways to do the same thing which I like to avoid because it makes it harder on users. |
I thought about this some more over the weekend, and my take on it is that this is plenty easy to do with a custom render method if your team really feels the need for these types of queries. My fears are still:
As @kentcdodds just said in another issue that was just closed, the limits of the testing-library API are intentional and considered a feature. Especially in native, this is a dangerous precedent because it would make it very easy to test by selecting any props sent to a native element rather than just the few users know about. Interested to hear if you have thought about it any more Kent? |
No more thoughts. |
I'm pretty against this. I feel like it's a more complicated API personally. |
Thank you for taking the time to open this. You could definitely build a "plugin" for react-testing-library and if it picks up a lot of usage then we can reconsider. Good luck! |
Describe the feature you'd like:
While considering options for testing-library/jest-dom#106, I found that the core API of dom-testing-library could be improved to make it more extensible. Currently each query defines 6 functions (
getByX
,getAllByX
,queryByX
,queryAllByX
,findByX
,findAllByX
). This means that implementing new queries requires a helper method (buildQueries
), requires exporting 6 functions, and if binding to an element, any required method must be bound (problematic for sub-libraries such asreact-testing-library
).This can be dramatically simplified with a fairly minor (and backwards-compatible) addition; I suggest a new API inspired by selenium's
By
pattern:Queries can be defined as so:
This has numerous advantages:
react-testing-library
'srender
would no-longer need to take aqueries
option; it would always bind these 6 methods, and any query can be used via an importattribute
query can take arguments as(name, value, ...options)
, allowingrole = attribute.bind(null, 'role')
to just work (the current implementation does the same but this makes the attribute query unusable by itself because it cannot take an element as its first parameter). This separation also makes configuration clearer (e.g.findBy
options are clearly forfindBy
, rather than for the specific query)labelText
would not need to define all custom methods to override its mismatch error message; it could define an error message lambda on the object which is used automatically if present (see possible implementation below)There are some disadvantages:
title
,text
,role
,testId
), although users can alias the imports if neededSuggested implementation:
The following "query runners" can be added to the existing default queries (in the long-run these could be the only bound methods):
Then queries can be defined using their existing implementations:
And since it's so easy to define new queries, smaller helpers could be added, such as:
Describe alternatives you've considered:
The exact naming could be tweaked. I considered
get
/getAll
/query
/queryAll
/find
/findAll
, moving theby
tobyTitle
/byAttribute
/ etc., which is closer to Selenium's choice, butget
,query
andfind
are even more likely to conflict!The original motivation behind this was to allow matchers (see the referenced issue). For example a matcher
expect(element).toContainElementWith(testId('foo'))
could be written, and it would get all the information it needs to generate useful error messages in all situations. An alternative solution was found to that problem, but I thought this API change was useful enough on its own to warrant suggesting.Teachability, Documentation, Adoption, Migration Strategy:
This can co-exist with the existing API, though I would recommend phasing out the old API as it will be possible to significantly simplify the codebase.
User migration is trivial:
The text was updated successfully, but these errors were encountered: