Web3前端测试

学习目标

  • 了解Web3前端测试的重要性
  • 掌握单元测试、集成测试和端到端测试的方法
  • 学习使用Jest、React Testing Library和Cypress等测试工具
  • 了解Web3特有的测试场景和解决方案

核心知识点

1. 测试类型概述

在Web3前端开发中,我们通常需要进行以下类型的测试:

  • 单元测试:测试单个组件或函数的功能
  • 集成测试:测试组件之间的交互
  • 端到端测试:测试完整的用户流程

2. 测试工具选择

2.1 单元测试工具

  • Jest:Facebook开发的JavaScript测试框架,支持快照测试和mock功能
  • React Testing Library:专注于测试组件的行为而非实现细节

2.2 端到端测试工具

  • Cypress:现代化的端到端测试框架,支持实时重载和时间旅行调试
  • Playwright:Microsoft开发的跨浏览器测试工具

3. Web3特有的测试场景

  • 钱包连接测试
  • 智能合约交互测试
  • 交易签名测试
  • 网络切换测试

实用案例分析

案例1:钱包连接组件测试

测试场景

测试钱包连接组件的功能,包括连接状态、错误处理和用户交互。

代码示例

// WalletConnectButton.test.js
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import WalletConnectButton from './WalletConnectButton';
import { useWeb3Context } from '../context/Web3Context';

// Mock Web3Context
jest.mock('../context/Web3Context', () => ({
  useWeb3Context: jest.fn()
}));

// Mock ethers.js
jest.mock('ethers', () => ({
  ethers: {
    providers: {
      Web3Provider: jest.fn(() => ({
        getSigner: jest.fn(() => ({
          getAddress: jest.fn().mockResolvedValue('0x1234567890123456789012345678901234567890')
        }))
      }))
    }
  }
}));

// Mock window.ethereum
const mockEthereum = {
  request: jest.fn(),
  on: jest.fn(),
  removeListener: jest.fn()
};

global.window.ethereum = mockEthereum;

describe('WalletConnectButton', () => {
  beforeEach(() => {
    useWeb3Context.mockReturnValue({
      account: null,
      connectWallet: jest.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'),
      isLoading: false
    });
  });

  test('renders connect button when not connected', () => {
    render(
      <BrowserRouter>
        <WalletConnectButton />
      </BrowserRouter>
    );
    expect(screen.getByText('连接钱包')).toBeInTheDocument();
  });

  test('renders account address when connected', () => {
    useWeb3Context.mockReturnValue({
      account: '0x1234567890123456789012345678901234567890',
      connectWallet: jest.fn(),
      isLoading: false
    });

    render(
      <BrowserRouter>
        <WalletConnectButton />
      </BrowserRouter>
    );
    expect(screen.getByText('0x123...7890')).toBeInTheDocument();
  });

  test('calls connectWallet when button is clicked', async () => {
    const { connectWallet } = useWeb3Context();
    
    render(
      <BrowserRouter>
        <WalletConnectButton />
      </BrowserRouter>
    );

    fireEvent.click(screen.getByText('连接钱包'));
    
    await waitFor(() => {
      expect(connectWallet).toHaveBeenCalled();
    });
  });

  test('shows loading state when connecting', () => {
    useWeb3Context.mockReturnValue({
      account: null,
      connectWallet: jest.fn(),
      isLoading: true
    });

    render(
      <BrowserRouter>
        <WalletConnectButton />
      </BrowserRouter>
    );
    expect(screen.getByText('连接中...')).toBeInTheDocument();
  });
});

案例2:智能合约交互测试

测试场景

测试前端与智能合约的交互功能,包括读取合约数据和发送交易。

代码示例

// TokenBalance.test.js
import { render, screen, waitFor } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import TokenBalance from './TokenBalance';
import { useWeb3Context } from '../context/Web3Context';

// Mock Web3Context
jest.mock('../context/Web3Context', () => ({
  useWeb3Context: jest.fn()
}));

// Mock token contract
const mockTokenContract = {
  balanceOf: jest.fn().mockResolvedValue('1000000000000000000') // 1 token
};

describe('TokenBalance', () => {
  beforeEach(() => {
    useWeb3Context.mockReturnValue({
      account: '0x1234567890123456789012345678901234567890',
      tokenContract: mockTokenContract,
      isLoading: false
    });
  });

  test('renders token balance correctly', async () => {
    render(
      <BrowserRouter>
        <TokenBalance />
      </BrowserRouter>
    );

    await waitFor(() => {
      expect(screen.getByText('代币余额: 1.00')).toBeInTheDocument();
    });
  });

  test('shows loading state when fetching balance', () => {
    useWeb3Context.mockReturnValue({
      account: '0x1234567890123456789012345678901234567890',
      tokenContract: mockTokenContract,
      isLoading: true
    });

    render(
      <BrowserRouter>
        <TokenBalance />
      </BrowserRouter>
    );
    expect(screen.getByText('加载中...')).toBeInTheDocument();
  });

  test('shows error message when balance fetch fails', async () => {
    mockTokenContract.balanceOf.mockRejectedValue(new Error('Failed to fetch balance'));

    render(
      <BrowserRouter>
        <TokenBalance />
      </BrowserRouter>
    );

    await waitFor(() => {
      expect(screen.getByText('获取余额失败')).toBeInTheDocument();
    });
  });
});

案例3:端到端测试

测试场景

使用Cypress测试完整的用户流程,包括钱包连接、查看余额和发送交易。

代码示例

// cypress/integration/web3-app.spec.js
describe('Web3 App', () => {
  beforeEach(() => {
    // 访问应用
    cy.visit('http://localhost:3000');
  });

  it('should connect wallet and display balance', () => {
    // 模拟MetaMask连接
    cy.window().then((win) => {
      // 模拟ethereum对象
      win.ethereum = {
        request: cy.stub().resolves(['0x1234567890123456789012345678901234567890']),
        on: cy.stub(),
        removeListener: cy.stub()
      };
    });

    // 点击连接钱包按钮
    cy.contains('连接钱包').click();

    // 验证钱包已连接
    cy.contains('0x123...7890').should('be.visible');

    // 验证余额显示
    cy.contains('代币余额:').should('be.visible');
  });

  it('should send transaction successfully', () => {
    // 模拟MetaMask连接
    cy.window().then((win) => {
      win.ethereum = {
        request: cy.stub()
          .onFirstCall().resolves(['0x1234567890123456789012345678901234567890'])
          .onSecondCall().resolves('0xtransactionhash'),
        on: cy.stub(),
        removeListener: cy.stub()
      };
    });

    // 连接钱包
    cy.contains('连接钱包').click();

    // 输入收款地址和金额
    cy.get('[data-testid="recipient-input"]').type('0x9876543210987654321098765432109876543210');
    cy.get('[data-testid="amount-input"]').type('0.1');

    // 点击发送按钮
    cy.contains('发送').click();

    // 验证交易成功
    cy.contains('交易成功').should('be.visible');
  });
});

常见问题解决方案

1. 如何模拟Web3环境?

解决方案:使用Jest的mock功能模拟window.ethereum对象和Web3相关库。

// 模拟window.ethereum
global.window.ethereum = {
  request: jest.fn(),
  on: jest.fn(),
  removeListener: jest.fn()
};

// 模拟ethers.js
jest.mock('ethers', () => ({
  ethers: {
    providers: {
      Web3Provider: jest.fn(() => ({
        getSigner: jest.fn(() => ({
          getAddress: jest.fn().mockResolvedValue('0x1234567890123456789012345678901234567890')
        }))
      }))
    }
  }
}));

2. 如何测试异步操作?

解决方案:使用async/await和waitFor来处理异步操作。

test('renders token balance correctly', async () => {
  render(
    <BrowserRouter>
      <TokenBalance />
    </BrowserRouter>
  );

  await waitFor(() => {
    expect(screen.getByText('代币余额: 1.00')).toBeInTheDocument();
  });
});

3. 如何测试智能合约交互?

解决方案:模拟合约实例和其方法。

// 模拟token合约
const mockTokenContract = {
  balanceOf: jest.fn().mockResolvedValue('1000000000000000000') // 1 token
};

// 在测试中使用
useWeb3Context.mockReturnValue({
  account: '0x1234567890123456789012345678901234567890',
  tokenContract: mockTokenContract,
  isLoading: false
});

总结

前端测试是Web3应用开发中不可或缺的一部分,它可以帮助开发者:

  1. 确保应用功能正常运行
  2. 减少回归bug
  3. 提高代码质量和可维护性
  4. 增强团队协作效率

通过本教程的学习,你已经掌握了Web3前端测试的基本方法和工具,包括单元测试、集成测试和端到端测试。在实际开发中,你应该根据项目的具体需求和规模,选择合适的测试策略和工具,确保应用的质量和可靠性。

« 上一篇 前端国际化 下一篇 » Web3前端部署