Open jonolo6 opened 4 years ago
Hi @jonolo6. Recoil requires components to be wrapped with <RecoilRoot>
so it has a context to orchestrate storing state.
One small way to reduce the need for the WrappedTodoItem
is to inline the wrapper where you call render, like this:
test("updates todo item", async () => {
const todo: Todo = {
id: "testId",
isComplete: false,
title: "old title",
};
// Act
const { getByTestId, getByDisplayValue } = render(
<RecoilRoot>
<RecoiledTodoItem todo={todo} />
</RecoilRoot>
);
fireEvent.change(getByTestId(/todo-text/i), {
target: { value: "new title" },
});
// Assert
expect(getByDisplayValue(/new title/i)).toBeInTheDocument();
});
You could create a custom render
function and use that in your tests.
// test-utils.js
import { render } from "@testing-library/react";
export {fireEvent} from "@testing-library/react";
export function render(elements) {
return render(<RecoilRoot>{elements}</RecoilRoot>);
}
// todo.test.js
import {render, fireEvent} from './test-utils';
...
const { getByTestId, getByDisplayValue } = render(
<RecoiledTodoItem todo={todo} />
);
Thx @acutmore . What annoys me most is that I've not been able to mock out useRecoilState<any>(todoListState);
which means I have to do the whole useEffect, map over the list malarkey in the RecoiledTodoItem
. Anyways - big thanks for the reply! Will leave it at this for now. Will test if I can use some jest.fn()
style mocking somehow...
@jonolo6 not sure if you've figured out your own pattern yet, but we ran into the same issue with testing out recoil states. We ended up creating a small library that extends @testing-library/react-hooks. It works by injecting RecoilRoot
into the wrapper and creating dummy components with state updaters for each piece of state you want to update. Check it out here https://github.com/inturn/react-recoil-hooks-testing-library would love some feedback!
This simple solution seems to work well if you just need to initialize some atom states before testing a component:
render(
<RecoilRoot initializeState={(snap) => snap.set(myAtom, {foo: 'bar'})}>
<MyComponent />
</RecoilRoot>,
);
The example by @acutmore is perfect if the updated state is reflected in the component being tested. What happens if the state is not reflected in the component being tested? One solution would be to create a dummy component for use in the test that did use this slice of state so that it could be checked. I can confirm that using the UNSTABLE snapshot API from the docs won't work as it's a different "copy" of the state graph.
Is there a way to spy on the atom or selector to see if it was updated and how? If not, is the only real way to test to use a dummy component as I'd previously mentioned? Finally, should I even be testing that the atom state was updated when an event happened in my component (my gut says yes)?
I've made a library to test recoil at the hook-level, to allow testing of data an async effects in isolation. dnsco/recoil-test-render-hooks if this helps anyone. It's very similar to inturn/react-recoil-hooks-testing-library in that it's a thin wrapper on the render-hooks testing library, but it allows you to build mutable scenarios and allows more strict typings.
I implemented the basic tutorial (https://recoiljs.org/docs/basic-tutorial/atoms) and wanted to add tests for components. I'm especially wondering how to mock out/simplify setting up global state that's accessed via recoil hooks. For example
useRecoilState
as below.Here's my test for the TodoItem class (it is in typescript, but that's not relevant). It works but I find it very convoluted to wrap my Component to test twice in order to get this to work correctly. I must be doing this in a very convoluted way.
Adding some example tests into an example/tests folder or similar using @testing-library/react (which is included in CRA) would be great!