CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-testing-library--react-hooks

Simple and complete React hooks testing utilities that encourage good testing practices.

Pending
Overview
Eval results
Files

server-side-rendering.mddocs/

Server-Side Rendering

Server-specific rendering capabilities for testing hooks in SSR environments with hydration support. This functionality is only available when using the server renderer from @testing-library/react-hooks/server.

Capabilities

Server renderHook

The server renderer provides the same renderHook API as other renderers but with additional server-specific functionality, including the ability to hydrate rendered components.

/**
 * Renders a React hook in a server environment with hydration support
 * @param callback - Function that calls the hook to be tested
 * @param options - Optional configuration including initial props and wrapper component
 * @returns ServerRenderHookResult with hydrate method and standard utilities
 */
function renderHook<TProps, TResult>(
  callback: (props: TProps) => TResult,
  options?: RenderHookOptions<TProps>
): ServerRenderHookResult<TProps, TResult>;

interface ServerRenderHookResult<TProps, TValue> extends RenderHookResult<TProps, TValue> {
  /** Hydrate the server-rendered markup on the client */
  hydrate(): void;
}

Usage Examples:

import { renderHook } from "@testing-library/react-hooks/server";
import { useState, useEffect } from "react";

// Server-side hook testing
function useServerState(initialValue: string) {
  const [value, setValue] = useState(initialValue);
  const [isClient, setIsClient] = useState(false);
  
  useEffect(() => {
    setIsClient(true); // This only runs on the client
  }, []);
  
  return { value, setValue, isClient };
}

test("server-side rendering", () => {
  const { result, hydrate } = renderHook(() => useServerState("server"));
  
  // On the server, useEffect hasn't run yet
  expect(result.current.value).toBe("server");
  expect(result.current.isClient).toBe(false);
  
  // Hydrate on the client
  hydrate();
  
  // After hydration, client-side effects run
  expect(result.current.isClient).toBe(true);
});

Hydration Method

The hydrate() method simulates the client-side hydration of server-rendered content, allowing you to test the transition from server to client rendering.

/**
 * Hydrate the server-rendered markup on the client side
 * This triggers client-side effects and state updates
 */
hydrate(): void;

Usage Examples:

function useHydrationEffect() {
  const [serverData, setServerData] = useState("server-data");
  const [clientData, setClientData] = useState(null);
  
  useEffect(() => {
    // This only runs on the client after hydration
    setClientData("client-data");
  }, []);
  
  return { serverData, clientData };
}

test("hydration effects", () => {
  const { result, hydrate } = renderHook(() => useHydrationEffect());
  
  // Server state
  expect(result.current.serverData).toBe("server-data");
  expect(result.current.clientData).toBe(null);
  
  // Hydrate to client
  hydrate();
  
  // Client effects have run
  expect(result.current.clientData).toBe("client-data");
});

// Testing hydration mismatches
function useConditionalRender() {
  const [isClient, setIsClient] = useState(false);
  
  useEffect(() => {
    setIsClient(true);
  }, []);
  
  // This creates a hydration mismatch
  return isClient ? "client" : "server";
}

test("hydration mismatch handling", () => {
  const { result, hydrate } = renderHook(() => useConditionalRender());
  
  expect(result.current).toBe("server");
  
  // Hydration will cause a mismatch but should resolve
  hydrate();
  
  expect(result.current).toBe("client");
});

Server Context Testing

Testing hooks that depend on server-side context or data fetching scenarios.

const ServerContext = React.createContext({ isServer: true });

function useServerContext() {
  const context = React.useContext(ServerContext);
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
  }, []);
  
  return { ...context, mounted };
}

test("server context with hydration", () => {
  const serverWrapper = ({ children }) => (
    <ServerContext.Provider value={{ isServer: true }}>
      {children}
    </ServerContext.Provider>
  );
  
  const clientWrapper = ({ children }) => (
    <ServerContext.Provider value={{ isServer: false }}>
      {children}
    </ServerContext.Provider>
  );
  
  const { result, hydrate } = renderHook(() => useServerContext(), {
    wrapper: serverWrapper
  });
  
  // Server state
  expect(result.current.isServer).toBe(true);
  expect(result.current.mounted).toBe(false);
  
  // Hydrate with client context
  hydrate();
  
  // Client state after hydration and effects
  expect(result.current.mounted).toBe(true);
});

SSR Data Fetching

Testing hooks that handle server-side data fetching and client-side rehydration.

function useSSRData(url: string) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [isSSR, setIsSSR] = useState(true);
  
  useEffect(() => {
    setIsSSR(false);
    
    if (!data) {
      // Client-side data fetching
      fetch(url)
        .then(response => response.json())
        .then(data => {
          setData(data);
          setLoading(false);
        });
    } else {
      // Data was server-rendered
      setLoading(false);
    }
  }, [url, data]);
  
  return { data, loading, isSSR };
}

test("SSR data fetching", async () => {
  // Mock server-rendered data
  const serverData = { id: 1, name: "Server Data" };
  
  const { result, hydrate, waitFor } = renderHook(() => {
    // Simulate server-side data injection
    const [initialData] = useState(serverData);
    return useSSRData("/api/data");
  });
  
  // Server state with pre-rendered data
  expect(result.current.isSSR).toBe(true);
  expect(result.current.loading).toBe(true);
  
  // Hydrate on client
  hydrate();
  
  // Wait for client-side effects
  await waitFor(() => !result.current.isSSR);
  
  expect(result.current.isSSR).toBe(false);
  expect(result.current.loading).toBe(false);
});

Error Boundaries in SSR

Testing error boundaries that behave differently on server vs client.

function useSSRErrorBoundary() {
  const [shouldError, setShouldError] = useState(false);
  const [isClient, setIsClient] = useState(false);
  
  useEffect(() => {
    setIsClient(true);
  }, []);
  
  if (shouldError && isClient) {
    throw new Error("Client-side error");
  }
  
  return { shouldError, setShouldError, isClient };
}

test("SSR error boundary behavior", () => {
  const { result, hydrate } = renderHook(() => useSSRErrorBoundary());
  
  expect(result.current.isClient).toBe(false);
  
  // Set error flag on server (no error thrown)
  act(() => {
    result.current.setShouldError(true);
  });
  
  expect(result.error).toBeUndefined();
  
  // Hydrate to client (error now thrown)
  expect(() => {
    hydrate();
  }).toThrow("Client-side error");
  
  expect(result.error).toBeInstanceOf(Error);
});

Best Practices for SSR Testing

Test Both Server and Client States:

test("complete SSR lifecycle", () => {
  const { result, hydrate } = renderHook(() => useMySSRHook());
  
  // Verify server state
  expect(result.current.serverProperty).toBeDefined();
  
  // Hydrate and verify client state
  hydrate();
  expect(result.current.clientProperty).toBeDefined();
});

Handle Hydration Timing:

test("hydration timing", async () => {
  const { result, hydrate, waitForNextUpdate } = renderHook(() => useAsyncSSRHook());
  
  hydrate();
  
  // Wait for async effects after hydration
  await waitForNextUpdate();
  
  expect(result.current.hydratedData).toBeDefined();
});

Mock Server Environment:

test("mock server environment", () => {
  // Mock server-specific globals or imports
  const originalWindow = global.window;
  delete global.window;
  
  const { result, hydrate } = renderHook(() => useWindowDetection());
  
  expect(result.current.hasWindow).toBe(false);
  
  // Restore window for client hydration
  global.window = originalWindow;
  hydrate();
  
  expect(result.current.hasWindow).toBe(true);
});

Install with Tessl CLI

npx tessl i tessl/npm-testing-library--react-hooks

docs

act-utilities.md

async-testing.md

cleanup-management.md

error-handling.md

hook-rendering.md

index.md

server-side-rendering.md

tile.json