| name | mock-to-unit |
| description | Convert fine-grain Cypress mock tests to Jest unit tests based on CI execution time |
| trigger | /mock-to-unit |
Mock-to-Unit Test Conversion
Converts fine-grain UI Cypress mock tests to faster Jest unit tests, focusing on CI execution time savings.
Conversion Criteria
CRITICAL RULE: Convert tests that assert RENDERED STATE ONLY with:
- ✅ NO user interaction (no clicks, typing, multi-step sequences)
- ✅ NO API waits (no
cy.wait('@alias') for network requests)
Formula: NO interaction + NO waits = FINE-GRAIN UI = CONVERT
KEEP in Cypress (workflow tests):
- ❌ User actions (clicks, typing, form submissions)
- ❌ API calls and waiting for responses
- ❌ Multi-step sequences
- ❌ Navigation between pages
- ❌ Modal/wizard flows with state transitions
Approach
1. Identify Bottleneck Files (CI Execution Time)
DO NOT use file size — focus on actual CI execution time from GitHub Actions runs.
Remember: Cypress mock tests run in parallel — total CI time = longest job time, NOT sum of all jobs.
2. Analyze Tests in Target File
For each test in the target file, count interactions:
import re
def count_interactions(test_content):
clicks = len(re.findall(r'\.click\(', test_content))
types = len(re.findall(r'\.type\(', test_content))
selects = len(re.findall(r'\.select\(', test_content))
checks = len(re.findall(r'\.check\(', test_content))
clears = len(re.findall(r'\.clear\(', test_content))
submits = len(re.findall(r'\.submit\(', test_content))
triggers = len(re.findall(r'\.trigger\(', test_content))
waits = len(re.findall(r'cy\.wait\(', test_content))
visits = len(re.findall(r'cy\.visit\(', test_content))
requests = len(re.findall(r'cy\.request\(', test_content))
intercepts = len(re.findall(r'cy\.intercept\(', test_content))
total = (clicks + types + selects + checks + clears + submits + triggers +
waits + visits + requests + intercepts)
KNOWN_CY = {
'get', 'findByTestId', 'findByRole', 'findByText', 'findByLabelText',
'findByPlaceholderText', 'findAllByTestId', 'findAllByRole', 'findAllByText',
'contains', 'find', , , , , , ,
, , , , , , , ,
, , , , , , , ,
, , , , , ,
}
KNOWN_CHAIN = {
, , , , , , , ,
, , , , ,
, , , ,
, , , , , , , ,
, , , , , ,
, , , , , ,
, , , , , , ,
, , ,
, , , , , , ,
}
cy_cmds = re.findall(, test_content)
chain_cmds = re.findall(, test_content)
has_unknown = (c KNOWN_CY c cy_cmds) \
(c KNOWN_CHAIN c chain_cmds)
has_unknown total == :
total == :
clicks == total == :
:
Expected conversion rate: 40-60% for typical files.
3. Check for Duplicate Coverage
Before converting, check if existing Jest unit tests already cover the same behavior:
find . -type f \( -name "*ConnectionsTable*.spec.*" -o -name "*ConnectionsTable*.test.*" \) \
-not -path "*/node_modules/*" -not -path "*/dist/*" -not -path "*/.cache/*"
grep -rl "ConnectionsTable" --include="*.spec.*" --include="*.test.*" \
frontend/ packages/ distributions/
Actions:
- Duplicate coverage → Remove Cypress test, add comment pointing to existing Jest test
- No existing coverage → Convert Cypress test to new Jest unit test
4. Convert Fine-Grain Tests to Jest
Target components:
- Table row display components
- Status labels
- Conditional rendering based on props
- Simple UI state (loading, error, empty states)
Conversion pattern:
it('Display project-scoped label for a notebook in workbenches table', () => {
initIntercepts();
projectDetails.visitSection('test-project', 'workbenches');
workbenchPage.findNotebookRow('test-notebook')
.findByTestId('project-scoped-label')
.should('exist');
});
it('should display project-scoped label when notebook uses project image', () => {
const notebook = mockNotebookK8sResource({
opts: {
metadata: {
annotations: {
'notebooks.opendatahub.io/last-image-selection': 'test-imagestream:1.2',
},
},
},
});
renderRow(notebook);
expect(screen.getByTestId('project-scoped-label')).toBeInTheDocument();
});
Key differences:
- ✅ No Cypress intercepts needed (mock data directly)
- ✅ No page navigation (render component directly)
- ✅ Significantly faster execution (measured ~390x faster for NotebookTableRow: ~50ms Jest vs ~19.5s Cypress)
- ✅ Isolated component testing
Note: Speedup varies by test complexity and system. Measured results show 100-1000x improvements for fine-grain UI tests. Always measure before/after on your specific tests.
5. Mock Data Pattern
ALWAYS use shared mock factories (from @odh-dashboard/internal/__mocks__ or type-owning packages):
import { mockNotebookK8sResource } from '#~/__mocks__/mockNotebookK8sResource';
import { mockNotebookState } from '#~/__mocks__/mockNotebookState';
import { mockProjectK8sResource } from '@odh-dashboard/k8s-core/__mocks__/mockProjectK8sResource';
const renderRow = (notebook = mockNotebookK8sResource({})) => {
const notebookState = mockNotebookState(notebook);
return render(
<MemoryRouter>
<ProjectDetailsContext.Provider value={mockContextValue}>
<table><NotebookTableRow obj={notebookState} /></table>
</ProjectDetailsContext.Provider>
</MemoryRouter>
);
};
6. Update Cypress Mock Test
Option A: If converted to Jest:
Option B: If duplicate of existing Jest test:
7. Verify and Measure
npm run test -- NotebookTableRow.spec.tsx
npm run test:cypress-ci -- --spec "**/workbench.cy.ts"
npm run lint:fix
Real-World Results
File: workbench.cy.ts (14m50s → target for conversion)
| Metric | Before | After | Improvement |
|---|
| Cypress test time | ~19.5s | Removed | N/A |
| Jest test time | N/A | ~50ms | 1800x faster |
| Jest test count | N/A | 6 tests | Better coverage |
| Total CI time | 14m50s | TBD | Target: 30-60s reduction |
File: connections.cy.ts (15m47s → target for conversion)
| Metric | Before | After | Improvement |
|---|
| Duplicate tests | 2 Cypress | 0 (deleted) | Reduced redundancy |
| Existing coverage | Jest unit | Jest unit | Already covered |
Skill Usage
/mock-to-unit analyze
/mock-to-unit convert packages/cypress/cypress/tests/mocked/projects/tabs/workbench.cy.ts
Quality Checklist
Before committing:
Common Mistakes
❌ Using file size instead of CI execution time — Size ≠ execution time
❌ Converting workflow tests — Tests with clicks/waits/API calls should stay in Cypress
❌ Not checking for duplicates — Remove redundant Cypress tests already covered by Jest
❌ Inline mock data — Always use shared mock factories (@odh-dashboard/internal/__mocks__ or type-owning packages)
❌ Forgetting to document removals — Add comments explaining what was converted/removed
✅ Focus on CI bottlenecks — Use actual GitHub Actions execution times
✅ Convert fine-grain UI only — NO interaction + NO waits = convert
✅ Check for duplicates first — Delete if Jest already covers it
✅ Measure before/after — Track actual CI time savings