Includes test utilities for scalprum scaffolding.
npm install @scalprum/react-test-utilsTesting utilities for Scalprum React applications
The @scalprum/react-test-utils package provides comprehensive testing utilities for Scalprum-based React applications. It simplifies testing micro-frontend components by mocking module federation, providing test providers, and setting up the necessary environment for testing federated modules.
``bash`
npm install @scalprum/react-test-utils --save-dev
- Mock Scalprum Environment: Complete Scalprum testing environment setup
- Plugin Data Mocking: Mock federated modules and plugin manifests
- Test Provider Component: Ready-to-use ScalprumProvider for tests
- Webpack Share Scope Mocking: Automatic webpack module federation mocking
- Fetch Polyfill: Built-in fetch polyfill for test environments
- Module Mocking: Easy mocking of remote federated modules
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen } from '@testing-library/react';
// Initialize Scalprum mocks once per test file
mockScalprum();
describe('MyComponent', () => {
it('renders with mocked plugin', () => {
const { TestScalprumProvider } = mockPluginData();
render(
);
expect(screen.getByTestId('default-module-test-id')).toBeInTheDocument();
});
});
`
Initializes the complete Scalprum testing environment. Call this once at the beginning of your test file.
`tsx
import { mockScalprum } from '@scalprum/react-test-utils';
// Set up Scalprum mocks before tests
mockScalprum();
describe('Scalprum Tests', () => {
// Your tests
});
`
What it does:
1. Mocks webpack share scope (__webpack_share_scopes__)
2. Adds fetch polyfill if not available
3. Sets up necessary global objects for module federation
Mocks the webpack module federation shared scope. Usually called via mockScalprum().
`tsx
import { mockWebpackShareScope } from '@scalprum/react-test-utils';
beforeAll(() => {
mockWebpackShareScope();
});
`
Creates:
`typescript`
globalThis.__webpack_share_scopes__ = {
default: {}
};
Adds fetch polyfill to the test environment. Usually called via mockScalprum().
`tsx
import { mockFetch } from '@scalprum/react-test-utils';
beforeAll(() => {
mockFetch();
});
`
Creates a complete mock setup for testing federated modules with custom configuration.
`tsx
import { mockPluginData, DEFAULT_MODULE_TEST_ID } from '@scalprum/react-test-utils';
import { render, screen } from '@testing-library/react';
describe('Plugin Module Tests', () => {
it('renders mocked module', () => {
const { TestScalprumProvider, response } = mockPluginData({
pluginManifest: {
name: 'my-plugin',
version: '1.0.0',
baseURL: 'http://localhost:3001',
loadScripts: ['plugin.js'],
extensions: [],
registrationMethod: 'custom'
},
module: 'MyExposedComponent',
moduleMock: {
default: () =>
render(
);
expect(screen.getByTestId('my-component')).toBeInTheDocument();
});
});
`
#### Parameters
`typescript
interface MockPluginDataOptions {
headers?: Headers;
url?: string;
type?: ResponseType;
ok?: boolean;
status?: number;
statusText?: string;
pluginManifest?: PluginManifest;
module?: string;
moduleMock?: ModuleMock;
config?: AppsConfig;
}
type ModuleMock = {
[importName: string]: React.ComponentType
};
`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| headers | Headers | new Headers() | Response headers |url
| | string | 'http://localhost:3000/test-plugin/plugin-manifest.json' | Manifest URL |type
| | ResponseType | 'default' | Response type |ok
| | boolean | true | Response ok status |status
| | number | 200 | HTTP status code |statusText
| | string | 'OK' | HTTP status text |pluginManifest
| | PluginManifest | Default manifest | Plugin manifest configuration |module
| | string | 'ExposedModule' | Module name to expose |moduleMock
| | ModuleMock | Default component | Mock module exports |config
| | AppsConfig | Auto-generated | Apps configuration |
Second Parameter:
`typescript`
api?: ScalprumProviderConfigurableProps['api']
Optional API context to pass to ScalprumProvider.
#### Returns
`typescript`
{
response: Response; // Mocked fetch response
TestScalprumProvider: React.ComponentType
}
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen } from '@testing-library/react';
import { ScalprumComponent } from '@scalprum/react-core';
mockScalprum();
describe('ScalprumComponent', () => {
it('loads and renders remote component', async () => {
const { TestScalprumProvider } = mockPluginData({
pluginManifest: {
name: 'dashboard',
version: '1.0.0',
baseURL: 'http://localhost:3001',
loadScripts: ['dashboard.js'],
extensions: [],
registrationMethod: 'custom'
},
module: 'Dashboard',
moduleMock: {
default: () =>
render(
module="Dashboard"
fallback={Loading...}
/>
);
expect(await screen.findByTestId('dashboard')).toBeInTheDocument();
});
});
`
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen } from '@testing-library/react';
mockScalprum();
describe('Widget Component', () => {
it('passes props to remote component', async () => {
const WidgetMock = ({ title, data }) => (
Data points: {data.length}
const { TestScalprumProvider } = mockPluginData({
module: 'Widget',
moduleMock: {
default: WidgetMock
}
});
render(
module="Widget"
title="Sales Chart"
data={[1, 2, 3, 4, 5]}
/>
);
expect(screen.getByText('Sales Chart')).toBeInTheDocument();
expect(screen.getByText('Data points: 5')).toBeInTheDocument();
});
});
`
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen } from '@testing-library/react';
import { useScalprum } from '@scalprum/react-core';
mockScalprum();
function ComponentUsingAPI() {
const { api } = useScalprum();
return
describe('Component with API', () => {
it('provides custom API context', () => {
const api = {
user: { id: '123', name: 'Test User' },
theme: 'dark'
};
const { TestScalprumProvider } = mockPluginData({}, api);
render(
);
expect(screen.getByText('User: Test User')).toBeInTheDocument();
});
});
`
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen } from '@testing-library/react';
mockScalprum();
describe('Multiple Modules', () => {
it('handles multiple exposed modules', async () => {
const { TestScalprumProvider } = mockPluginData({
module: 'MainComponent',
moduleMock: {
default: () =>
function TestApp() {
return (
<>
module="MainComponent"
importName="SecondaryComponent"
/>
>
);
}
render(
);
expect(await screen.findByTestId('main')).toBeInTheDocument();
expect(await screen.findByTestId('secondary')).toBeInTheDocument();
});
});
`
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen } from '@testing-library/react';
mockScalprum();
describe('Error Handling', () => {
it('handles failed plugin loading', () => {
const { TestScalprumProvider } = mockPluginData({
ok: false,
status: 404,
statusText: 'Not Found'
});
function ErrorComponent({ error }) {
return
render(
module="Missing"
ErrorComponent={
/>
);
// Component should handle the error gracefully
});
});
`
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen, waitFor } from '@testing-library/react';
import { useModule } from '@scalprum/react-core';
mockScalprum();
function TestComponent() {
const Widget = useModule('test-plugin', 'Widget');
if (!Widget) {
return
return
}
describe('useModule Hook', () => {
it('loads module with useModule', async () => {
const WidgetMock = ({ title }) =>
const { TestScalprumProvider } = mockPluginData({
module: 'Widget',
moduleMock: {
default: WidgetMock
}
});
render(
);
await waitFor(() => {
expect(screen.getByTestId('widget')).toBeInTheDocument();
});
});
});
`
`tsx
import { mockScalprum, mockPluginData } from '@scalprum/react-test-utils';
import { render, screen, waitFor } from '@testing-library/react';
import { useRemoteHook } from '@scalprum/react-core';
import { useMemo, useState } from 'react';
mockScalprum();
function ComponentWithRemoteHook() {
const args = useMemo(() => [{ initialValue: 0 }], []);
const { hookResult, loading, error } = useRemoteHook({
scope: 'test-plugin',
module: 'useCounter',
args
});
if (loading) return
return
describe('Remote Hooks', () => {
it('loads and executes remote hook', async () => {
const useCounterMock = ({ initialValue }) => {
const [count, setCount] = useState(initialValue);
return { count, increment: () => setCount(c => c + 1) };
};
const { TestScalprumProvider } = mockPluginData({
module: 'useCounter',
moduleMock: {
default: useCounterMock
}
});
render(
);
await waitFor(() => {
expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
});
});
});
`
Constant for the default test ID used by the default module mock.
`tsx
import { DEFAULT_MODULE_TEST_ID } from '@scalprum/react-test-utils';
expect(screen.getByTestId(DEFAULT_MODULE_TEST_ID)).toBeInTheDocument();
`
Value: 'default-module-test-id'
For Jest testing, add initialization to your setup file:
jest.setup.js
`javascript
import { mockScalprum } from '@scalprum/react-test-utils';
// Initialize Scalprum mocks globally
mockScalprum();
`
jest.config.js
`javascript`
module.exports = {
setupFilesAfterEnv: ['
testEnvironment: 'jsdom',
// ... other config
};
1. Call mockScalprum() once per test file - Usually at the top level
2. Create fresh mocks per test - Call mockPluginData() inside each testfindBy
3. Use async/await with - Remote modules load asynchronouslymockPluginData()
4. Mock only what you need - Don't over-configure ok: false
5. Test error states - Use to test error handling
6. Provide API context - Use second parameter for components needing shared API
Full TypeScript support with type definitions:
`tsx
import { mockPluginData } from '@scalprum/react-test-utils';
import { PluginManifest } from '@openshift/dynamic-plugin-sdk';
interface MyComponentProps {
title: string;
data: number[];
}
const manifest: PluginManifest = {
name: 'my-plugin',
version: '1.0.0',
baseURL: 'http://localhost:3001',
loadScripts: ['plugin.js'],
extensions: [],
registrationMethod: 'custom'
};
const MyComponentMock: React.ComponentType
{title}: {data.length} items
);
const { TestScalprumProvider } = mockPluginData({
pluginManifest: manifest,
moduleMock: {
default: MyComponentMock
}
});
`
This package is compatible with:
- Jest - Recommended test runner
- React Testing Library - For component testing
- Vitest - Modern alternative to Jest
- Any test framework that supports jsdom environment
`json`
{
"dependencies": {
"@openshift/dynamic-plugin-sdk": "^5.0.1",
"@scalprum/core": "^0.8.3",
"@scalprum/react-core": "^0.9.5",
"whatwg-fetch": "^3.6.0"
}
}
Problem: Component shows loading state indefinitely
Solution: Ensure you're using findBy queries (async) instead of getBy:
`tsx
// ❌ Wrong - synchronous query
expect(screen.getByTestId('my-component')).toBeInTheDocument();
// ✅ Correct - async query
expect(await screen.findByTestId('my-component')).toBeInTheDocument();
`
Problem: __webpack_share_scopes__ is not defined
Solution: Call mockScalprum() before running tests:
`tsx
import { mockScalprum } from '@scalprum/react-test-utils';
mockScalprum(); // Add this
describe('Tests', () => {
// ...
});
`
Problem: fetch is not defined in test environment
Solution: mockScalprum() includes fetch polyfill. If called separately, use:
`tsx
import { mockFetch } from '@scalprum/react-test-utils';
mockFetch();
`
`typescript
// Main utilities
export function mockScalprum(): void;
export function mockWebpackShareScope(): void;
export function mockFetch(): void;
export function mockPluginData(
options?: MockPluginDataOptions,
api?: ScalprumProviderConfigurableProps['api']
): {
response: Response;
TestScalprumProvider: React.ComponentType
};
// Constants
export const DEFAULT_MODULE_TEST_ID: string;
// Re-exports from other packages
export { useScalprum, ScalprumProvider } from '@scalprum/react-core';
export type { AppsConfig } from '@scalprum/core';
export type { PluginManifest } from '@openshift/dynamic-plugin-sdk';
`
- @scalprum/core - Framework-agnostic core library
- @scalprum/react-core - React components and hooks
- @scalprum/build-utils` - Build tools and NX executors
Apache-2.0