Mocking Patterns in Waldur HomePort
This document outlines the standard practices, patterns, and anti-patterns for mocking in the Waldur HomePort project. Adhering to these guidelines ensures test consistency, maintainability, and clarity.
Rationale
Mocking is essential for isolating the component under test by replacing complex dependencies (like API clients, global state, or third-party libraries) with controlled substitutes. In Waldur HomePort, we aim to:
- Reduce Boilerplate: Centralize common mocks to avoid repetitive setup in every test file.
- Improve Type Safety: Use
vi.mocked()for better TypeScript support when interacting with mocks. - Ensure Isolation: Reset mocks between tests to prevent state leakage.
- Simulate Real Scenarios: Provide realistic mock responses that mimic the behavior of the actual dependencies.
Centralized Mocks
Most common service dependencies are mocked globally in test/setupTests.js. This ensures they are consistently available and pre-configured.
Key Global Mocks
@/core/config: Provides a defaultENVobject with common nested structures (plugins.WALDUR_CORE,FEATURES).@/i18n: Mockstranslateand JSX formatters to return literal strings or simple fragments.waldur-js-client: Automatically mocked to prevent actual API calls.@/modal/actions: ProvidesuseModalwith a defaultconfirmthat resolves successfully.@/store/notify: ProvidesuseNotifyfor toast notifications.@/workspace/hooks: Mocks hooks likeuseUser,useCustomer, anduseProject.@/router&@uirouter/react: Globally mocked. Provides a standardrouterobject and common hooks (useRouter,useCurrentStateAndParams,useTransition).@phosphor-icons/react: Globally mocked using a Proxy. Icons are replaced with<span>elements containingdata-testid="icon-IconName".@/i18n: Mockstranslateand JSX formatters to return literal strings or simple fragments.
Test Harness and Providers
Avoid manual QueryClient setup. Use the renderWithProviders utility from @/test/harness to wrap components in necessary providers (like QueryClientProvider).
Example: Using renderWithProviders
1 2 3 4 5 6 7 | |
If you need to inspect or spy on the queryClient (e.g., for invalidations):
1 2 3 4 5 6 7 8 9 10 | |
Recommended Patterns
1. Using vi.mocked() for Type Safety
Always wrap mocked functions in vi.mocked() to get proper autocompletion and type checking for Vitest mock methods.
1 2 3 4 5 6 | |
2. Overriding Global Mocks for Specific Tests
If a global mock needs a specific behavior for one test case, use .mockResolvedValueOnce() or similar.
1 2 3 4 5 6 | |
3. Clearing Mocks in beforeEach
Ensure vi.clearAllMocks() is called in beforeEach to start every test with a clean slate.
1 2 3 | |
Global Mock Examples
API Client (waldur-js-client)
The entire client is mocked by default in test/setupTests.js. Use vi.mocked() to provide specific responses.
1 2 3 4 5 6 7 8 9 10 11 | |
Modals (useModal)
The confirm method resolves successfully by default.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Notifications (useNotify)
Assertions on toast notifications are common. Use vi.mocked() on the methods of useNotify().
1 2 3 4 5 6 7 8 9 | |
Workspace Hooks (@/workspace/hooks)
These are pre-configured to return empty objects by default.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Routing (@uirouter/react)
Routing is globally mocked. You rarely need to define vi.mock('@uirouter/react') locally.
Asserting on Navigation
Import router from @/router to assert on navigation calls:
1 2 3 4 5 6 7 8 | |
Mocking State or Parameters
Use vi.mocked() on hooks from @uirouter/react to simulate being on a specific page or having specific URL params:
1 2 3 4 5 6 7 8 | |
Using a Real Router
If your test requires a real router instance (e.g. for complex state transition logic), use createTestRouter from @/test/router:
1 2 3 4 5 6 7 8 9 | |
Application Configuration (@/core/config)
The ENV object is globally mocked. Since Vitest isolates test files, you can safely modify ENV at the top level of your test file without affecting other files.
1 2 3 4 5 6 7 8 9 10 11 | |
Icons (@phosphor-icons/react)
Icons are mocked globally using a Proxy. This keeps screen.debug() clean and allows for easy assertions on icons.
1 2 3 4 5 6 7 | |
❌ Manual @tanstack/react-query Mocking
Avoid: Manually mocking useQueryClient or creating a new QueryClient() inside individual tests.
Better: Use renderWithProviders.
❌ Inline Mock Definitions in Components
Avoid: Defining mock data or functions directly in the test file's top level if they can be reused or belong in a fixture.
Better: Use src/user/support/fixtures.ts or similar shared fixture files.
❌ Deep Mocking of Internal Implementation
Avoid: Mocking internal component state or private methods. Better: Mock the external dependencies and assert on the observable behavior (DOM changes, service calls).
❌ Hardcoded Wait Times
Avoid: Using setTimeout or fixed delays in tests.
Better: Use waitFor() from @testing-library/react.
Summary Table
| Dependency | How to Mock / Use |
|---|---|
| API Calls | vi.mocked(apiFunction).mockResolvedValue(...) |
| Notifications | expect(useNotify().showSuccess).toHaveBeenCalled() |
| Modals | vi.mocked(useModal().confirm).mockResolvedValue(true) |
| React Query | Use renderWithProviders |
| Translations | Handled globally; just use translate as normal |
| Routing | Handled globally; import router from @/router or hooks from @uirouter/react and use vi.mocked() |