UPDATE: When I was writing this, I was using the 3.1.3 version of the library I think, now I've updated and some things have changed. I have revised the article, if I missed something tell me in the comments.
If you're using the actual version, above 5, then here are some changes to these snippets:
- No renderIntoDocument. Use render method
- No need to change the values first, and then use fireEvent. Use it like this:
fireEvent(component.getByLabelText("Some label"), {target: { value: "20"}});
- You dont check checkboxes with .change on fireEvent, use .click.
I think I've got them all.
Disclaimer: I am describing some use cases, I would consider myself useful. I hope this post helps somebody, and if not, maybe it will help myself when I forget something.
What I use to test react is: Jest and react-testing-library for unit tests and cypress for integration (or end to end, I'm not sure how should I call these). I'm not going to talk about cypress here, though.
localStorage is not defined
One of the first problems I stumbled upon was that localstorage is not defined, when you run tests.
And that was when I got to know about mocks. Basically, you can substitute (mock) some code if you don't really need to test it, at least not in this test case.
In this instance, browser APIs like localstorage are not defined in your testing environment, and you can mock it the following way:
//browserMocks.js
var localStorageMock = (function() {
var store = {};
return {
getItem: function(key) {
return store[key] || null;
},
setItem: function(key, value) {
store[key] = value.toString();
},
clear: function() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
});
After that you're going to need to tell jest to use this file in your testing environment. For that, open your package.json file and edit it according to the following snippet:
"jest": {
"setupFiles": [
"./some_path/browserMocks.js",
]
}
Mocking with Jest
Like in the example above, you sometimes have to mock some of your code. It either slows down the testing, because it makes API calls, or it's too hard to set up, it gives errors, whatever. You can isolate your test and mock everything non-essential to it. You can do it like this:
jest.mock("../path/module", () => {
return jest.fn();
});
More Mocking
One time I needed to mock only one method on a class. I didn't want to substitute a whole class with a mock like presented above. I could've written a mock class for it, and define methods there, and tell jest to use that, but that seemed like too much work.
What I did was the following:
ClassName.prototype.method= jest.fn()
Thanks to the user under nickname WickyNilliams from stackoverflow. You can checkout his more detailed answer here
Testing something is in the DOM or not
React-testing-library gives you two methods for accesing rendered elements, one starts with queryBy and the other with getBy. There are a few of those methods, like getByTestId or queryByTestId.
You can read more about it in the readme of the library, which methods you need particularly. But the difference between getting an element with a method that starts with queryBy is that it can be null and getBy always should return an element or it will throw an error.
So, if you want to test that something is not in the DOM, you can do the following:
const component = renderIntoDocument(<Component />);
expect(component.queryByTestId("testid-of-element")).toBeNull();
But, what if you want to test that something is in the DOM. For that you need to install jest-dom/extend-expect, and then you can do this:
const component = renderIntoDocument(<Component />);
expect(component.queryByTestId("row-34")).toBeInTheDOM();
Test that some element has a certain class
const component = renderIntoDocument(<Component />);
expect(component.getByTestId("testid-element")
.classList.contains("class-name")).toBe(true);
Of course you can pass false and test that it doesn't have some class.
You don't have testids
Suppose, you don't have testids for these elements within rendered component, and you want to test something.
const component = renderIntoDocument(<Component />);
const elements = component.container.getElementsByClassName("class-name");
There are other ways to access elements, exposed by react-testing-library. Like getByLabelText and a few others, you can see here. But sometimes none of them apply, so I use classes like I presented above. But, it's probably not a good idea, or at least not a best practice. Because someone can rename that class, or remove it, and your tests will fail. I just thought, I should mention that there are other ways. You can also use getElementsByTagName.
Events
From react-testing-library you can import fireEvent and trigger some events for react to handle. This is really useful, because a lot of times I need to test the state of the component after something happened. Triggering the click is pretty easy, but the others are a bit tricky. Well, at least for me, I spent some time trying to figure out how to trigger the change in some cases.
const component = renderIntoDocument(<Component />);
fireEvent.click(component.getByTestId('testid-element'));
The interesting thing is, that if you want to trigger onChange handler, you have to make the change first in your testing code and then trigger onchange. Like, let's say you have an input and want to test it's onChange handler:
const component = renderIntoDocument(<Component />);
component.getByTestId("input").value = "20";
fireEvent.change(component.getByTestId("input"), {});
If you want to test a checkbox, there is a thing I stumbled upon. You can trigger onchange like this:
const component = renderIntoDocument(<Component />);
component.getByLabelText("Label Text").setAttribute("checked", "");
fireEvent.change(component.getByLabelText("Label Text"));
But, this method didn't work for me, when my input had defaultChecked property.
What did work in that situation for me is this:
const component = renderIntoDocument(<Component />);
component.getByLabelText("Label Text").checked = true;
fireEvent.change(component.getByLabelText("Label Text"));
I have no idea why this happens, I would probably go with the last one every time for safety.
Timers
In case something in your code should happen after a number of seconds, for example, you're using setTimeout method, you can use fake timers and just see the result. For that, use jest's method:
jest.useFakeTimers();
After you've executed this method, you can use it in your tests:
jest.runAllTimers();
Unmount components in tests
I used renderIntoDocument in the examples, if you're going to use this method, don't forget to clean up with unmount method.
const component = renderIntoDocument(<Component />);
/// testing
component.unmount();
Top comments (0)