Testing strategies for Module Federation need to consider module independence and dependency relationships. Here is a complete testing solution:
1. Unit Testing
Testing Remote Modules:
javascript// Button.test.js import { render, screen } from '@testing-library/react' import Button from './Button' describe('Button Component', () => { it('renders button with correct text', () => { render(<Button label="Click me" />) expect(screen.getByText('Click me')).toBeInTheDocument() }) it('calls onClick handler when clicked', () => { const handleClick = jest.fn() render(<Button label="Click me" onClick={handleClick} />) screen.getByText('Click me').click() expect(handleClick).toHaveBeenCalledTimes(1) }) })
Testing Shared Dependencies:
javascript// sharedDependency.test.js import React from 'react' describe('Shared React Dependency', () => { it('uses singleton instance', () => { const react1 = require('react') const react2 = require('react') expect(react1).toBe(react2) }) it('maintains correct version', () => { const react = require('react') expect(react.version).toMatch(/^17\./) }) })
2. Integration Testing
Testing Module Loading:
javascript// moduleLoading.test.js import { render, screen, waitFor } from '@testing-library/react' describe('Remote Module Loading', () => { it('loads remote component successfully', async () => { const RemoteComponent = React.lazy(() => import('remoteApp/Button') ) render( <React.Suspense fallback={<div>Loading...</div>}> <RemoteComponent label="Remote Button" /> </React.Suspense> ) await waitFor(() => { expect(screen.getByText('Remote Button')).toBeInTheDocument() }) }) it('handles loading errors gracefully', async () => { const FallbackComponent = () => <div>Fallback</div> const RemoteComponent = React.lazy(() => import('remoteApp/NonExistent') .catch(() => import('./Fallback')) ) render( <React.Suspense fallback={<div>Loading...</div>}> <RemoteComponent /> </React.Suspense> ) await waitFor(() => { expect(screen.getByText('Fallback')).toBeInTheDocument() }) }) })
Testing Inter-Module Communication:
javascript// interModuleCommunication.test.js describe('Inter-Module Communication', () => { it('shares state between modules', async () => { const { createStore } = await import('remoteApp/store') const store = createStore() store.dispatch({ type: 'SET_VALUE', payload: 'test' }) expect(store.getState().value).toBe('test') }) it('emits events across modules', async () => { const { eventBus } = await import('remoteApp/eventBus') const handler = jest.fn() eventBus.on('test-event', handler) eventBus.emit('test-event', { data: 'test' }) expect(handler).toHaveBeenCalledWith({ data: 'test' }) }) })
3. End-to-End Testing
Testing with Cypress:
javascript// cypress/integration/app.spec.js describe('Module Federation E2E', () => { it('loads remote module on page load', () => { cy.visit('/') cy.get('[data-testid="remote-button"]').should('be.visible') cy.get('[data-testid="remote-button"]').should('contain', 'Remote Button') }) it('interacts with remote component', () => { cy.visit('/') cy.get('[data-testid="remote-button"]').click() cy.get('[data-testid="message"]').should('contain', 'Button clicked') }) it('handles module loading failure', () => { cy.intercept('GET', '**/remoteEntry.js', { forceNetworkError: true }) cy.visit('/') cy.get('[data-testid="fallback-message"]').should('be.visible') }) })
Testing with Playwright:
javascript// app.spec.ts import { test, expect } from '@playwright/test' test.describe('Module Federation E2E', () => { test('loads remote module successfully', async ({ page }) => { await page.goto('/') const button = page.locator('[data-testid="remote-button"]') await expect(button).toBeVisible() await expect(button).toHaveText('Remote Button') }) test('handles remote module interactions', async ({ page }) => { await page.goto('/') await page.click('[data-testid="remote-button"]') const message = page.locator('[data-testid="message"]') await expect(message).toContainText('Button clicked') }) })
4. Performance Testing
Module Loading Performance Test:
javascript// performance.test.js describe('Module Loading Performance', () => { it('loads remote module within acceptable time', async () => { const startTime = performance.now() await import('remoteApp/Button') const loadTime = performance.now() - startTime expect(loadTime).toBeLessThan(2000) // Load within 2 seconds }) it('measures bundle size', () => { const stats = require('./webpack-stats.json') const remoteEntrySize = stats.assets.find( asset => asset.name === 'remoteEntry.js' ).size expect(remoteEntrySize).toBeLessThan(100 * 1024) // Less than 100KB }) })
5. Mocking Remote Modules
Using Jest Mock:
javascript// __mocks__/remoteApp/Button.js export default function MockButton({ label }) { return <button data-testid="mock-button">{label}</button> } // test file jest.mock('remoteApp/Button') import Button from 'remoteApp/Button' describe('Mocked Remote Module', () => { it('uses mocked component', () => { render(<Button label="Mocked Button" />) expect(screen.getByTestId('mock-button')).toBeInTheDocument() }) })
Using MSW to Intercept Requests:
javascript// msw.js import { setupServer } from 'msw/node' import { rest } from 'msw' const server = setupServer( rest.get('http://localhost:3001/remoteEntry.js', (req, res, ctx) => { return res( ctx.set('Content-Type', 'application/javascript'), ctx.body(` var remoteApp; (function() { remoteApp = { get: function(module) { return import('./' + module + '.js'); }, init: function(sharedScope) { // initialization logic } }; })(); `) ) }) ) beforeAll(() => server.listen()) afterEach(() => server.resetHandlers()) afterAll(() => server.close())
6. Test Configuration
Jest Configuration:
javascript// jest.config.js module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', moduleNameMapper: { '\\.(css|less|scss|sass)$': 'identity-obj-proxy', '^@/(.*)$': '<rootDir>/src/$1', '^remoteApp/(.*)$': '<rootDir>/__mocks__/remoteApp/$1' }, transform: { '^.+\\.(ts|tsx|js|jsx)$': 'ts-jest' }, setupFilesAfterEnv: ['<rootDir>/jest.setup.js'] }
Cypress Configuration:
javascript// cypress.config.js const { defineConfig } = require('cypress') module.exports = defineConfig({ e2e: { baseUrl: 'http://localhost:3000', supportFile: 'cypress/support/e2e.js', specPattern: 'cypress/integration/**/*.cy.{js,jsx,ts,tsx}', viewportWidth: 1280, viewportHeight: 720 } })
7. Testing Best Practices
- Isolated Tests: Each test runs independently without depending on other tests
- Mock External Dependencies: Use mocks to isolate external dependencies
- Cover Edge Cases: Test success, failure, timeout scenarios
- Performance Benchmarks: Set performance baselines and monitor performance degradation
- Continuous Integration: Integrate tests in CI/CD
- Test Coverage: Maintain high test coverage (>80%)
- Documentation: Add comments and documentation for complex tests
Through these testing strategies, the quality and stability of Module Federation applications can be ensured.