Lesson 12-Micro Frontend Testing Strategies

Testing Pyramid Application

Unit Testing Strategies

Core Principles

  • Isolation: Each test case should run independently, without relying on external environments
  • Single Responsibility: Each test verifies only one functionality
  • Fast Feedback: Unit tests should complete in milliseconds

Implementation Approach

// React component unit test example (Jest + React Testing Library)
import { render, screen } from '@testing-library/react';
import Button from './Button';

describe('Button Component', () => {
  it('renders with default props', () => {
    render(<Button>Click Me</Button>);
    expect(screen.getByText('Click Me')).toBeInTheDocument();
  });

  it('handles click events', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click Me</Button>);
    userEvent.click(screen.getByText('Click Me'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

Best Practices

  1. Target 70-80% coverage for critical logic
  2. Use mocks to isolate external dependencies
  3. Follow the AAA pattern (Arrange-Act-Assert)

Integration Testing Strategies

Test Scope

  • Component interactions
  • Inter-module communication
  • Data flow validation

Implementation Approach

// Micro frontend communication integration test example
import { eventBus } from './eventBus';

describe('Microfrontend Communication', () => {
  it('should handle user login event', () => {
    const mockHandler = jest.fn();
    eventBus.on('user-login', mockHandler);
    
    eventBus.emit('user-login', { id: 1, name: 'Test User' });
    
    expect(mockHandler).toHaveBeenCalledWith({ id: 1, name: 'Test User' });
  });
});

Challenges and Solutions

  1. Cross-Application Dependencies: Use test doubles (stubs/mocks)
  2. Environment Consistency: Use Docker for containerized test environments
  3. Performance Considerations: Run test cases in parallel

End-to-End (E2E) Testing Strategies

Testing Pyramid Positioning

  • Base: Unit Tests (70%)
  • Middle: Integration Tests (20%)
  • Top: E2E Tests (10%)

Implementation Approach (Cypress Example)

// Micro frontend application E2E test
describe('Microfrontend Application', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('loads all microfrontends', () => {
    cy.get('[data-testid="app1"]').should('be.visible');
    cy.get('[data-testid="app2"]').should('be.visible');
  });

  it('handles cross-app navigation', () => {
    cy.get('[data-testid="navigate-to-app2"]').click();
    cy.url().should('include', '/app2');
    cy.get('[data-testid="app2-content"]').should('exist');
  });
});

Key Considerations

  1. Test Data Management: Use tools like Factory Boy to generate test data
  2. Environment Isolation: Ensure independent environments for each test case
  3. Execution Efficiency: Selectively run critical path E2E tests

Micro Frontend-Specific Testing

Application Isolation Testing

Testing Goals

  • Ensure sub-applications do not pollute the global namespace
  • Verify style isolation effectiveness
  • Confirm JavaScript sandbox functionality

Testing Approach

// Style isolation test example
describe('Style Isolation', () => {
  it('should not leak styles to parent app', () => {
    // Load sub-application
    loadMicroApp('app1');
    
    // Verify parent app styles are unaffected
    expect(document.body.style.backgroundColor).not.toEqual('red');
    
    // Verify sub-app styles are confined to container
    const appContainer = document.getElementById('app1-container');
    expect(appContainer.querySelector('.child-style')).toHaveStyle('color: blue');
  });
});

Communication Mechanism Testing

Test Scenarios

  • Event bus communication
  • Custom protocol communication
  • Shared state synchronization

Implementation Example

// Event bus communication test
describe('Event Bus Communication', () => {
  it('should propagate events between apps', (done) => {
    // Sub-application listens for event
    window.addEventListener('custom-event', (e) => {
      expect(e.detail).toEqual({ data: 'test' });
      done();
    });
    
    // Main application triggers event
    window.dispatchEvent(new CustomEvent('custom-event', {
      detail: { data: 'test' }
    }));
  });
});

Loading Process Testing

Testing Key Points

  • Loading performance metrics
  • Error handling paths
  • Fallback mechanisms

Testing Approach

// Loading performance test
describe('Microfrontend Loading Performance', () => {
  it('should load within performance budgets', async () => {
    const start = performance.now();
    await loadMicroApp('app1');
    const duration = performance.now() - start;
    
    expect(duration).toBeLessThan(3000); // Complete loading within 3 seconds
  });
  
  it('should show fallback when loading fails', async () => {
    // Simulate loading failure
    jest.spyOn(window, 'fetch').mockRejectedValue(new Error('Failed to load'));
    
    render(<MicroAppLoader name="app1" />);
    expect(screen.getByText('Loading failed')).toBeInTheDocument();
  });
});

Automated Testing Frameworks

Jest Configuration and Usage

Base Configuration (jest.config.js)

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy'
  },
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  transform: {
    '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
  },
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/index.tsx',
    '!src/serviceWorker.ts'
  ],
};

Testing Library Integration

// jest.setup.js
import '@testing-library/jest-dom';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

Cypress/E2E Testing

Project Initialization

npm install cypress --save-dev
npx cypress open

Key Configuration (cypress.json)

{
  "baseUrl": "http://localhost:3000",
  "video": false,
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "defaultCommandTimeout": 10000,
  "requestTimeout": 15000
}

Test Example

// cypress/integration/app_spec.js
describe('Microfrontend App', () => {
  beforeEach(() => {
    cy.visit('/');
  });

  it('loads the app', () => {
    cy.contains('Welcome to Micro Frontend').should('be.visible');
  });

  it('handles user login', () => {
    cy.get('[data-testid="login-btn"]').click();
    cy.get('[data-testid="username"]').type('testuser');
    cy.get('[data-testid="login-submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});

Playwright Application

Advantage Comparison

FeatureCypressPlaywright
Browser SupportChrome, EdgeChromium, Firefox, WebKit
Parallel ExecutionLimitedNative support
Mobile TestingNot supportedSupported
Network InterceptionLimitedRobust

Test Example

// playwright/test/app.spec.js
const { test, expect } = require('@playwright/test');

test.describe('Microfrontend App', () => {
  test('loads successfully', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('[data-testid="app-header"]')).toHaveText('Micro Frontend');
  });

  test('handles navigation', async ({ page }) => {
    await page.goto('/');
    await page.click('[data-testid="navigate-to-app2"]');
    await expect(page).toHaveURL(/\/app2/);
  });
});

Test Coverage and Quality

Coverage Metrics Setting

MetricUnit TestsIntegration TestsE2E Tests
Statement Coverage≥80%≥70%≥50%
Branch Coverage≥70%≥60%≥40%
Function Coverage≥85%≥75%
Line Coverage≥80%≥70%

Dynamic Threshold Strategy

// Dynamically adjust thresholds based on project complexity
function getCoverageThresholds(projectSize) {
  if (projectSize === 'small') {
    return { statements: 70, branches: 60, functions: 75, lines: 70 };
  } else if (projectSize === 'medium') {
    return { statements: 80, branches: 70, functions: 85, lines: 80 };
  } else {
    return { statements: 90, branches: 80, functions: 90, lines: 85 };
  }
}

Test Quality Assessment

Quality Metrics System

  1. Stability: Test pass rate fluctuation <5%
  2. Reliability: False positive rate <10%
  3. Maintainability: Test code complexity <1.5x main code
  4. Value: Defect detection rate >70%

Visualization Dashboard Example

// Test quality dashboard data
const testQualityMetrics = {
  stability: {
    currentPassRate: 98.2,
    historicalTrend: [98.5, 98.3, 98.7, 98.1, 98.4]
  },
  reliability: {
    falsePositives: 8,
    totalTests: 850
  },
  maintainability: {
    complexityRatio: 1.3,
    techDebt: 15 // Person-days
  }
};

Continuous Integration Testing

CI/CD Integration Strategy

# GitHub Actions example
name: CI Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14.x, 16.x]
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
    
    - name: Install dependencies
      run: npm ci
      
    - name: Run Unit Tests
      run: npm test -- --coverage
      
    - name: Run E2E Tests
      run: npm run e2e
      
    - name: Upload Coverage
      uses: codecov/codecov-action@v1

Test Parallelization Strategy

// Jest parallel configuration
module.exports = {
  maxWorkers: '50%', // Use 50% of CPU cores
  testMatch: ['**/__tests__/**/*.js?(x)', '**/?(*.)+(spec|test).js?(x)'],
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname'
  ],
  // Test sharding
  shard: {
    total: 3,
    current: 1
  }
};

Test Result Analysis and Reporting

// Test report generation configuration
const { createReport } = require('multiple-cucumber-html-reporter');

createReport({
  jsonDir: 'cypress/cucumber-json',
  reportPath: './cypress/report/cucumber_report.html',
  metadata: {
    browser: {
      name: 'chrome',
      version: '91'
    },
    device: 'Local test machine',
    platform: {
      name: 'ubuntu',
      version: '20.04'
    }
  }
});

Summary and Recommendations

  1. Testing Strategy Selection:
    • Unit tests cover core business logic (70-80%)
    • Integration tests validate critical paths (20%)
    • E2E tests ensure end-to-end flows (10%)
  2. Micro Frontend-Specific Considerations:
    • Add application isolation testing
    • Strengthen communication mechanism validation
    • Optimize loading process testing
  3. Framework Selection Recommendations:
    • Unit Testing: Jest + Testing Library
    • Integration Testing: Cypress/Playwright
    • E2E Testing: Cypress (simple scenarios)/Playwright (complex scenarios)
  4. Quality Assurance System:
    • Establish dynamic coverage thresholds
    • Implement test quality assessment metrics
    • Enhance CI/CD testing workflows
  5. Continuous Improvement Directions:
    • Automate test data management
    • Containerize test environments
    • Explore AI-assisted test case generation

By building a comprehensive testing system, micro frontend architectures can significantly improve software quality, reduce maintenance costs, and provide robust support for continuous delivery. Start with foundational unit tests, gradually enhance testing at all levels, and ultimately form a comprehensive automated testing safety net.

Share your love