Open childrentime opened 2 years ago
Sorry, did you mean to create this issue?
Sorry, missing some words. how can i migratie example used in ssr environment with @testing-library/react?
This question is better suited for @testing-library/react
, i'll move the issue.
Hello 👋 Tiny up on this conversation :)
I faced almost the same problem as OP and wanted to share my solution here in case someone else stumbles over this issue.
First I extracted the code I needed from @testing-library/react-hooks/server
and updated it to be compatible with react@18 (hydrateRoot
). I also skipped a lot of code I didn't need for my case, so you might need to extend my renderHookServer
function to match your specific case.
import type { ReactNode } from 'react';
import { hydrateRoot } from 'react-dom/client';
import { renderToString } from 'react-dom/server';
import { act } from 'react-dom/test-utils';
export const renderHookServer = <Hook extends () => any>(
useHook: Hook,
{
wrapper: Wrapper,
}: {
wrapper?: ({ children }: { children: ReactNode }) => JSX.Element;
} = {}
): { result: { current: ReturnType<Hook> }; hydrate: () => void } => {
// Store hook return value
const results: Array<ReturnType<Hook>> = [];
const result = {
get current() {
return results.slice(-1)[0];
},
};
const setValue = (value: ReturnType<Hook>) => {
results.push(value);
};
const Component = ({ useHook }: { useHook: Hook }) => {
setValue(useHook());
return null;
};
const component = Wrapper ? (
<Wrapper>
<Component useHook={useHook} />
</Wrapper>
) : (
<Component useHook={useHook} />
);
// Render hook on server
const serverOutput = renderToString(component);
// Render hook on client
const hydrate = () => {
const root = document.createElement('div');
root.innerHTML = serverOutput;
act(() => {
hydrateRoot(root, component);
});
};
return {
result: result,
hydrate: hydrate,
};
};
I also had to add this to jest setupFiles:
import { TextEncoder } from 'util';
global.TextEncoder = TextEncoder;
Finally I was able to use it like this:
import { renderHookServer } from '../../testing/renderHookServer';
import { useHasMounted } from '../useHasMounted';
describe('useHasMounted', () => {
it('returns false first and then true after hydration', () => {
const { result, hydrate } = renderHookServer(useHasMounted);
expect(result.current).toBe(false);
hydrate();
expect(result.current).toBe(true);
});
});
It literally freaks me out that so called "merge"of libraries cut out half of functionality and in case you need to test hooks agains SSR environment - "screw you - go make your own testing library for that, you're not welcome here".
Same here: I was looking to upgrade Docusaurus to React 18 and couldn't find a simple official solution to migrate this test:
import React from 'react';
import {renderHook} from '@testing-library/react-hooks/server';
import {BrowserContextProvider} from '../browserContext';
import useIsBrowser from '../exports/useIsBrowser';
describe('BrowserContextProvider', () => {
const {result, hydrate} = renderHook(() => useIsBrowser(), {
wrapper: ({children}) => (
<BrowserContextProvider>{children}</BrowserContextProvider>
),
});
it('has value false on first render', () => {
expect(result.current).toBe(false);
});
it('has value true on hydration', () => {
hydrate();
expect(result.current).toBe(true);
});
});
The docs say:
hydrate: If hydrate is set to true, then it will render with ReactDOM.hydrate. This may be useful if you are using server-side rendering and use ReactDOM.hydrate to mount your components.
https://testing-library.com/docs/react-testing-library/api#hydrate
It is unclear to me how to use this option and how it "may be useful". Who has ever used it in practice and how? An example would be very welcome
Same for usehooks-ts
, how to migrate that
import { renderHook as renderHookCsr } from '@testing-library/react-hooks/dom'
import { renderHook as renderHookSsr } from '@testing-library/react-hooks/server'
import { useIsClient } from './useIsClient'
describe('useIsClient()', () => {
it('should be false when rendering on the server', (): void => {
const { result } = renderHookSsr(() => useIsClient())
expect(result.current).toBe(false)
})
it('should be true when after hydration', (): void => {
const { result, hydrate } = renderHookSsr(() => useIsClient())
hydrate()
expect(result.current).toBe(true)
})
it('should be true when rendering on the client', (): void => {
const { result } = renderHookCsr(() => useIsClient())
expect(result.current).toBe(true)
})
})
@eps1lon hello, can you have a notice on this. I find the render option has {hydrate: true}, but it don't use renderToString like the old behavior, it is directly using hydrateRoot to the test component. So we can't get the value by renderToString in server anymore.
Should result
contain the earliest possible result or the result after all the data has streamed in?
@eps1lon I think it is the earliest possible reuslt, which means the value when react render in server return, should not changed by effects
There is something important in react, like we should return the same value in server and client at the first render, so there must have a way to check it
Is there an update on this?
I tried cloning the repository and modifying the source code, but it was difficult to implement. The reason is that for a container, once you have already used createRoot
to call it, you cannot use hydrateRoot
to continue calling it. So, I think there should be a separate test function for React 18 that returns the createRoot
and hydrateRoot
functions. The hooks should only be rendered when you call that function, instead of rendering the hooks when you use renderHook()
.
There is a simpler approach where we can add a renderToString function to the return value of renderHook, which would return the result of calling the hook with ReactDomServer.renderToString
.
Hi, Guys. What do you think?
@eps1lon It is evident that the current code does not meet the requirements for React server testing because your container does not include the server-side HTML. Therefore, it will not detect common React errors like Warning: Expected server HTML to contain a matching <div> in <div>
Would a bundler or React server be necessary for testing some SSR or RSC hooks? I'm thinking about possible architectures for server components, and if there's overlap with server hooks.
I think it's generally unnecessary to consider React server components. Custom hooks are typically used only in client-side components. https://github.com/facebook/react/blob/493f72b0a7111b601c16b8ad8bc2649d82c184a0/packages/react/src/ReactSharedSubset.js
Yes, but custom hooks should still be able to use the following React hooks in RSCs:
Hello everyone. This is how I currently conduct Server Side Rendering (SSR) testing, and I hope it can serve as a reference for you. https://github.com/childrentime/reactuse/pull/81
it("should throw mismatch during hydrating when not set default state in dark", async () => {
const TestComponent = createTestComponent(() => usePreferredDark());
const element = document.createElement("div");
document.body.appendChild(element);
try {
const markup = ReactDOMServer.renderToString(<TestComponent />);
element.innerHTML = markup;
window.matchMedia = createMockMediaMatcher({
"(prefers-color-scheme: dark)": true,
}) as any;
await act(() => {
return ReactDOMClient.hydrateRoot(element, <TestComponent />);
});
expect((console.error as jest.Mock).mock.calls[0].slice(0, 3))
.toMatchInlineSnapshot(`
[
"Warning: Text content did not match. Server: "%s" Client: "%s"%s",
"false",
"true",
]
`);
}
finally {
document.body.removeChild(element);
}
});
The test environment "* @jest-environment ./.test/ssr-environment" is copied from the React source code. I found that it seems that this is how testing is done in the React source code.
I encountered this issue. We need to verify that our custom hooks are returning the appropriate thing on initial render (i.e. the server render). I made this function as a drop-in replacement (ymmv, I only covered our use cases) for the old renderHook
import from @testing-library/react/server
.
Perhaps this will be useful to others who are missing the old function for verifying their hooks do the right thing before effects are run.
import * as React from "react";
import * as ReactDOMServer from "react-dom/server";
type Options<Props> = {
/**
* Pass a React Component as the wrapper option to have it rendered around the inner element. This is most useful for creating
* reusable custom render functions for common data providers.
*/
wrapper?: React.JSXElementConstructor<{children: React.ReactNode}>;
initialProps?: Props;
};
type RenderHookServerResult<Result> = {
result: {
current: Result;
};
};
/**
* Render a hook within a test React component as if on the server.
*
* This is useful for seeing what the initial render might be for a hook before
* any effects are run.
*/
export const renderHookServer = <Result, Props>(
render: (initialProps: Props) => Result,
{wrapper, initialProps}: Options<Props> = {},
): RenderHookServerResult<Result> => {
let result: Result;
function TestComponent({renderCallbackProps}) {
result = render(renderCallbackProps);
return null;
}
const component = <TestComponent renderCallbackProps={initialProps} />;
const componentWithWrapper =
wrapper == null
? component
: React.createElement(wrapper, null, component);
ReactDOMServer.renderToString(componentWithWrapper);
// @ts-expect-error Variable 'result' is used before being assigned. ts(2454)
return {result: {current: result}};
};
With @testing-library/react-hooks not supporting React 19, this matter is even more burning than it was before.
What would renderHookServer
offer other than
function Component() {
return useHookUnderTest()
}
const result = ReactDOMServer.renderToString(
?
For assertions before and after hydration, the proposed API is problematic since it creates a mixed client/server environment that you'd never encounter in the real world. Server-only and client-only tests should give you most of the coverage and the remaining hydration bits can be tested with e2e tests that give you the proper confidence.
What is your question:
How can I migrate this code from
@testing-library/react-hooks
to@testing-library/react
to use react18 and use the ssr environment?old code