I'm trying to test a Reactnode with react, vitest and the teasting libary.
But the formatting from the innerHTML wil not match. How I can aviod the formatting for testing cases?
- Expected - 3
+ Received + 1
- <div>
- content
- </div>
+ <div>content</div>
Here my test code
const content: ReactElement = (<div>content</div>);
makeSUT('title 2', content);
const contentResult = await screen.findByTestId('modal-content');
expect(contentResult).toBeInTheDocument();
expect(contentResult.innerHTML).toEqual(content);
Thank you
As you can see, using innerHTML in that manner can be flaky and fail due to something as simple as difference in newlines, which is not how you want to be testing your application(s) and is irrelevant to whether it is working properly.
Instead of comparing the innerHTML of two <div> elements, to include their semantic tags, I recommend using findByText to determine if the text exists in the document.
For example:
const contentWrapper = screen.getByTestId('custom-element');
const contentResult = screen.findByText('content');
// If the component with the test id is in the document, it rendered
expect(contentWrapper).toBeInTheDocument();
// contentResult will not be find the text if it is not there and this will fail
expect(contentResult).to.exist;
Really though, the assertions above are redundant as "content" won't be in the document if the component didn't render, and getByText will throw an error if no text is found (both in the assertion, and because of how getByText functions.
Example:
const contentResult = screen.findByText('content');
expect(contentResult).to.exist;
Why do I recommend using getByText over using a test id? Testing library (which you are using and is not specific to Vitest) documentation recommends use of test ids to query elements as being the least preferable/recommended query method.
From the testing library documentation: "The user cannot see (or hear) these, so this is only recommended for cases where you can't match by role or text or it doesn't make sense (e.g. the text is dynamic).", which is in contrast to their principles of "your test should resemble how users interact with your code (component, page, etc.) as much as possible."
To see a working example of what I have described, I created a demo on StackBlitz.
Related
How to test a functional component that is using a fragment and props, but is not using markup, I cannot set a role.
const Item = (props: ItemProps) => (
<React.Fragment>
{ props.content }
</React.Fragment>
);
I don't see what assurance would a test give you on this particular component.
Assert whatever is in props.content. If you're using react-testing-library (as you tagged the question with that), just use the corresponding query, this article from the official docs should help deciding which query is appropriate, based on your logic (that you haven't shared in your question).
You always want to test the behaviour, rather than the implementation, this is especially true with the front-end and with react-testing-library, like the creator of the library pointed out as well. So don't be too hang up on this particular component, look inside see what you can test there.
<Abc>
<Xyz data-testid="comp-xyz" prop1={pqr}/>
</Abc>
Here if Abc is a class component and we are testing Abc using react testing library, then is there a way to test the value of prop1 ?
const {getByTestId} = render(<Abc />)
Now I grab the component Xyz using testId
getByTestId("comp-xyz")
Can I get props of this component something like this ?
getByTestId("comp-xyz").props() ?
No. The idea of the testing-library is to encourage you to test the user's interaction with the screen.
Creating tests that depend on component data, such as props and state, does not help with this type of test,
because users do not interact directly with the props, but with the elements rendered through them.
Another point is that tests that depend on implementation details are fragile tests, that is,
any change in the implementation, which does not change the behavior of the component, can fail its testing
generating a 'false-negative'.
In this documentation link they explain it better: https://testing-library.com/docs/
This might help someone:
const { fixture } = await render(AppComponent)
const componentInstance = fixture.componentInstance as AppComponent
expect(componentInstance.prop).toBe(true)
https://testing-library.com/docs/angular-testing-library/api/#fixture
Note that this approach is discouraged in the docs, but sometimes you gotta do what you gotta do.
I'd like to test the selection logic of my Select component.
https://codesandbox.io/s/eager-violet-kkw0x?file=/src/App.js
I found this snippet to test a simulation.
test("simulates selection", () => {
const { getByTestId, getAllByTestId } = render(<Select />);
fireEvent.change(getAllByTestId("select"), { target: { value: 2 } });
let options = getAllByTestId("select-option");
expect(options[0].selected).toBeFalsy();
expect(options[1].selected).toBeTruthy();
expect(options[2].selected).toBeFalsy();
});
However, it fails with TypeError: Cannot read property 'map' of undefined. What's going on? Thank you.
Short answer: you need to make sure you do a conditional check to skip whenever your values are undefined. You can do so by sticking in values && just before values.map(...
Secondly, I really don't see the value of using a two dimensional array just for having an index. You can store a list of fruits in an array and iterate through with a simple map, passing in the value both as a value and as an key. If you want to go fancy with the key, you can even assign the number increment that is generated by the .map()'s index along with the value, here is the fancy version:
{values && values.map((index, value) => (
<option key={`${index}-${value}`} value={value} data-testid="select">
{value}
</option>
))}
The above would generate a key like fruit-1, anotherfruit-2, etc, however you can just go with the fruit name, whatever you do, make sure you don't just go with the index number as that is not a good practice. This is all when you have a simple array, as I said, you don't need a two dimensional one.
Moving forward, there are a lot of issues in your test and some problems in the code, so it won't work unless reimagining the whole code and test, however I give my best to try and explain some of the problems and point you to the right direction:
First line with the issue is:
fireEvent.change(getAllByTestId("select"), { target: { value: 2 } });
You want to select one element, that is the <select /> so you need to use getByTestId instead of getAllByTestId, you also got the id wrong, it is select-option.
The correct format looks like this:
fireEvent.change(getByTestId("select-option"), { target: { value: 2 } });
Just a quick note, while the above works, there are other, better ways of doing this, I recommend looking into the user-event library instead of fireEvent, but most importantly, use a getByRole instead of getByTestId, read why
Next problem is that you haven't passed in your props to your select component, therefore there are no <option> elements when rendered. You couldn't do this mistake with something like TypeScript as it would warn you, but JavaScript doesn't, so.. need to pass in the props:
const props = {
callback: jest.fn(),
values: [
"grapefruit", "coconut", "lime", "mango"
],
selected: 'lime'
}
const { getByTestId, getAllByTestId } = render(<Select {...props} />);
Moving forward, when collecting options, you used the wrong ID again (I think you mixed up the two) and I would also recommend using queryAll rather than getAll, this is because queryAll would return an empty array while getAll would throw an error.
const options = queryAllByTestId("select");
Finally your assertions are all wrong as well. Your option won't have a selected attribute what then you can boolean evaluate. Your option has two attributes data-testid and value.
I tried to give you the best answer to understand what is going on, but as the code stands, it's impossible to fix it without rethinking the whole app and test, like I mentioned above so here is my advice:
In the components:
Change to two dimensional array and use the value as the index.
You are not doing anything with the selected value, just console logging out, if you pass it back to your parent component, save it into the parent state and do whatever you want with it, Probably display somewhere - this is actually important for the test.
selected prop has a string, that will probably need to have the value that you pass back and save it to the state in the parent.
In the test:
React testing library is great because your tests resemble to how the users would use the app, therefore you should assert if the selected component appears where you want, instead of trying to inspect attributes, that would work the following way:
Make sure you render the component that will have both the select component and the component you will render the selected value to as text.
Make sure you pass in all props to the component you rendering with rtl
Simulate action (recommend the user-event library, but fireEvent will also work)
use the getByText to see if the value rendered and exists on the page. That would be something like: expect(getByText('fruitName')).toBeInTheDocument();
Finally, I recommend looking into the selector precedence (use getByRole where you can) and the difference between getBy and queryBy.
Here's a line from one of my React Testing Library tests:
expect(queryByTestId('tile-sample-service')).toHaveClass('regularTile-0-2-24', 'btn', 'btn-outline-secondary');
While it works, the test is fragile because every time the structure of the component changes, I need to go back and fix the numbers, which have changed.
Is there a way to use toHaveClass with RegEx queries or is there some other way to check if classes are present but avoid having to add things like "0-2-24" ?
Yeah for some CSS in JS generated class names sometimes the number suffix changes.
Something like this should work:
const currentLinkAnchorEl = getByText(container, 'My Currently Active Link').closest('a');
expect(currentLinkAnchorEl.className).toMatch(/mySelectedActiveClassName/)
I think it's not possible with toHaveClass(...classNames: string[]),but you can use Shallow Renderer,try this one
import ShallowRenderer from 'react-test-renderer/shallow';
it('match claas name', () => {
const renderer = new ShallowRenderer();
renderer.render(<Component />);
expect(renderer.getRenderOutput().props.className).toMatch(/button/i);
})
I am using contentful to get markdown to a react component that uses react-markdown to parse the markdown
import ReactMarkdown from 'react-markdown';
<Markdown source={text} />
Would I like to do is to override the Renderer so instead of it rendering ## as an h2 render i can pass a custom component to override the default h2 type to my own h2 component. How can i do that and is there and examples?
One of the options to <ReactMarkdown> is renderers.
One of the common renderers handles headings. If you look at the default rendering you'll see this:
heading: function Heading(props) {
return createElement('h' + props.level, getCoreProps(props), props.children);
},
So pass in your own heading handler. Check the level inside, roughly:
function CustomHeading(props) {
if (props.level !== 2) {
return createElement(`h${props.level}`, getCoreProps(props), props.children);
}
return <MyCustomElement {...props} />
}
If you don't have access to the code that commonmark-react-renderer gives you in the context of your function (which you probably won't) then you'd also need to duplicate what createElement gives you (but it's simple).
Unrelated: I've never used <ReactMarkdown> (but will), but this took me about five minutes of research. I'm including my path to encourage others to dig into their own questions and hopefully give some insight into how such things can be researched.
The react-markdown home page
Scanned through the "Options" section to see if custom rendering was trivially supported
Found the renderers option, which sounded promising
Clicked the link provided in that option's docs
Saw that heading was one of those (which made sense; I'd expect a renderer for every major formatting that Markdown supports)
Opened up the src directory to see if the implementation was easy to find
There was only one file, so I opened it
Searched the page for "heading" and found it
Cut and pasted that code here
The ability to read docs and follow trails is really important.