Closed adrianbw closed 3 years ago
I would recommend not to test like that, rather test the overall functionality by asserting on changes that should happen on the UI/screen.
I would recommend not to test like that, rather test the overall functionality by asserting on changes that should happen on the UI/screen.
I think the question is around how to unit test this function in isolation. UI Testing has its place of course. However, devs/teams may be structured in a way that UI testing is not doable by the dev writing this functionality + unit testing is good practice anyway for code that is reused in many other places (speeds up build, etc.).
Hi @adrianbw. Recoil requires React to run. You can test your Recoil Atoms and Selectors by creating a small React component that uses them and testing that in the way you would test any React component. https://reactjs.org/docs/testing.html
For example:
const React = require("react");
const TestRenderer = require("react-test-renderer");
const Recoil = require("recoil");
const mySelector = require("../path/to/your/selector");
test("...", () => {
let value = null;
function TestSelector() {
const selectorValue = Recoil.useRecoilValue(mySelector);
React.useEffect(() => {
value = selectorValue;
});
return null;
}
TestRenderer.act(() => {
TestRenderer.create(<Link page="https://www.facebook.com/">Facebook</Link>);
});
expect(value).toEqual(expectedValue);
});
If you want to just test your state update function and not run Recoil then you could extract, export and test that code in isolation.
Before:
export const setAddComplete = (
addComplete: boolean,
setNotesState: SetterOrUpdater<State>
) => {
setNotesState((state) => {
return {
...state,
addComplete,
};
});
};
After:
// Can directly test this function without needed Recoil
export updateState(state, addComplete) {
return {...state, addComplete };
}
export const setAddComplete = (
addComplete: boolean,
setNotesState: SetterOrUpdater<State>
) => {
setNotesState(updateState);
};
For anyone who comes upon this and likes to write unit tests (a discussion I'm not going to get into), here's a pattern I adopted, which uses jest.fn()
to report out the atom's value.
interface IGenericUpdater<ValType, StateType extends object> {
(value: ValType, setState: SetterOrUpdater<StateType>): void;
}
interface ISetterTest<U, V extends object> {
atom: Atom;
function: IGenericUpdater<U, V>;
value: U;
}
type UpdaterFunction = (property: any, updaterFunction: SetterOrUpdater<any>) => void;
type TestComponentProps = {
atom: Atom;
function: UpdaterFunction;
value: any;
reporterFunction: (state: State) => void;
};
const TestComponent: React.FunctionComponent<TestComponentProps> = (props: TestComponentProps) => {
const [state, setState]: [State, any] = useRecoilState(props.atom);
React.useEffect(() => {
props.function(props.value, setState);
props.reporterFunction(state);
}, [state]);
return <div />;
};
const createMountedWrapper = (additionalProps: Partial<TestComponentProps>) => {
const props = {
atom: null as any,
function: jest.fn() as any,
value: null as any,
reporterFunction: jest.fn() as any,
...additionalProps,
};
const wrapper = mount(<RecoilRoot><TestComponent {...props} /></RecoilRoot>).rendered;
const instance = wrapper.find(TestComponent).instance() as any;
return {wrapper, instance, ...props};
};
describe('recoil tests', () => {
let componentWrapper: any;
afterEach(() => {
if (componentWrapper?.unmount) {
componentWrapper.unmount();
}
});
it('setAddComplete', () => {
const props: ISetterTest<boolean, State> = {
atom: notesStore,
function: setAddComplete,
value: true,
};
const { wrapper, reporterFunction } = createMountedWrapper(props);
componentWrapper = wrapper;
expect(reporterFunction).toHaveBeenCalledWith({...initState, addComplete: props.value});
});
});
Before adding wrapper.unmount()
, I got in some loops with useEffect testing multiple functions (presumably because it was adding an instance on every mount?). There may be other ways to prevent this that I don't know of.
You can now use snapshots for testing outside of React.
What's the best method for testing functions around Recoil? For example, if I had a function like this:
How should I test it? I noticed the TestingUtils folder, but it looks like they aren't included in the package.