Testing Patterns (@data-client/test)
Hook Testing with renderDataHook()
import { renderDataHook } from '@data-client/test';
it('useSuspense() should render the response', async () => { const { result, waitFor } = renderDataHook( () => useSuspense(ArticleResource.get, { id: 5 }), { initialFixtures: [ { endpoint: ArticleResource.get, args: [{ id: 5 }], response: { id: 5, title: 'hi ho', content: 'whatever' }, }, ], }, ); expect(result.current.title).toBe('hi ho'); });
Options:
-
initialFixtures
-
Set up initial state of the store
-
resolverFixtures
-
Add interceptors for subsequent requests
-
getInitialInterceptorData
-
Simulate changing server state
Return values:
-
Inherits all renderHook() return values from @testing-library/react
-
controller
-
Controller instance for manual actions
-
allSettled()
-
Wait for all async operations to complete
Cleanup is automatic -- an afterEach hook is registered at module load time that drains all active cleanups. No manual renderDataHook.cleanup() calls are needed.
Fixtures and Interceptors
Success Fixture:
interface SuccessFixture { endpoint; args; response; error?; delay?; }
Response Interceptor:
interface ResponseInterceptor { endpoint; response(...args); delay?; delayCollapse?; }
Testing Mutations
Create operations:
it('should create a new todo', async () => { const { result } = renderDataHook( () => useController(), { initialFixtures: [ { endpoint: TodoResource.getList, args: [], response: [], }, ], resolverFixtures: [ { endpoint: TodoResource.getList.push, response: (newTodo) => ({ ...newTodo, id: 1 }), }, ], }, );
const newTodo = { title: 'Test Todo', completed: false }; const createdTodo = await result.current.fetch(TodoResource.getList.push, newTodo);
expect(createdTodo.id).toBe(1); });
Testing Error States
it('should handle fetch errors', async () => { const { result, waitFor } = renderDataHook( () => useSuspense(TodoResource.get, { id: 1 }), { initialFixtures: [ { endpoint: TodoResource.get, args: [{ id: 1 }], response: null, error: new Error('Not found'), }, ], }, );
await waitFor(() => { expect(result.current).toBeUndefined(); }); });
Testing Components
Use MockResolver to provide fixture data when rendering components with DataProvider :
import { render } from '@testing-library/react'; import { DataProvider } from '@data-client/react'; import { MockResolver } from '@data-client/test';
it('should render todo list', async () => { const fixtures = [ { endpoint: TodoResource.getList, args: [], response: [{ id: 1, title: 'Test Todo', completed: false }], }, ];
const { getByText } = render( <DataProvider> <MockResolver fixtures={fixtures}> <TodoList /> </MockResolver> </DataProvider>, );
expect(getByText('Test Todo')).toBeInTheDocument(); });
Testing with nock (HTTP Endpoint Testing)
import nock from 'nock';
it('should fetch data from API', async () => { const scope = nock('https://jsonplaceholder.typicode.com') .get('/todos/1') .reply(200, { id: 1, title: 'Test', completed: false });
const result = await TodoResource.get({ id: 1 });
expect(result.title).toBe('Test'); scope.done(); });
Testing Managers
it('should handle manager middleware', async () => { const mockManager = { middleware: (controller) => (next) => async (action) => { if (action.type === 'FETCH') { console.log('Fetch action:', action); } return next(action); }, cleanup: jest.fn(), };
const { controller } = renderDataHook( () => useController(), { managers: [mockManager] }, );
await controller.fetch(TodoResource.get, { id: 1 }); expect(mockManager.cleanup).not.toHaveBeenCalled(); });
Test File Organization
Keep tests under packages/*/src/**/tests :
packages/react/src/hooks/tests/useSuspense.test.ts packages/react/src/components/tests/DataProvider.test.tsx
Test naming:
-
Node-only: *.node.test.ts[x]
-
React Native: *.native.test.ts[x]
-
Regular: *.test.ts[x]
Best Practices
-
Use renderDataHook() for testing hooks that use @data-client/react hooks
-
Use fixtures or interceptors when testing hooks or components
-
Use nock when testing networking definitions
-
Test both success and error scenarios
-
Test mutations and their side effects
-
Don't mock @data-client internals directly
-
Don't use raw fetch in tests when fixtures are available
-
Don't manually call renderDataHook.cleanup() in afterEach -- cleanup is automatic
References
For detailed API documentation, see the references directory:
-
renderDataHook - Hook testing utility
-
makeRenderDataHook - Custom hook renderer
-
Fixtures - Fixture format reference
-
MockResolver - Component testing wrapper
-
mockInitialState - Create initial state
-
unit-testing-hooks - Hook testing guide
-
unit-testing-components - Component testing guide