Solana[part18]_Solana DAPP-前端单元测试与合约接入

Solana DAPP-前端单元测试与合约接入

一、初始化 Solana DApp 项目

1. 环境准备

先确保安装基础工具:

# 安装 Node.js(推荐 v16+)
# 安装 Solana CLI(用于本地节点和钱包管理)
sh -c "$(curl -sSfL https://release.solana.com/v1.18.4/install)"

# 检查版本
solana --version
node --version

2. 初始化项目

使用官方模板创建项目:

npx create-solana-dapp@latest my-solana-dapp
cd my-solana-dapp

3. 项目结构解析(核心文件)

my-solana-dapp/
├── app/                  # 前端代码(React)
├── programs/             # 后端程序(Rust)
│   └── my_solana_dapp/   # 核心逻辑
├── tests/                # 测试文件(TypeScript)
│   └── my-solana-dapp.ts # 测试用例
├── Anchor.toml           # 项目配置(集群、程序ID等)
└── package.json          # 依赖管理

二、测试文件编写(TypeScript)

测试文件位于 tests/ 目录,用于验证程序逻辑,后端学员需重点关注以下几点:

1. 测试基本结构

// tests/my-solana-dapp.ts
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { MySolanaDapp } from "../target/types/my_solana_dapp"; // 自动生成的类型

describe("my-solana-dapp", () => {
  // 1. 初始化环境
  const provider = anchor.AnchorProvider.env(); // 使用环境变量配置的集群
  anchor.setProvider(provider);
  const program = anchor.workspace.MySolanaDapp as Program<MySolanaDapp>; // 加载程序
  const wallet = provider.wallet as anchor.Wallet; // 测试钱包

  it("初始化一个计数器", async () => {
    // 2. 准备账户(比如创建一个计数器账户)
    const counterKeypair = anchor.web3.Keypair.generate(); // 随机生成账户

    // 3. 调用程序指令(比如初始化计数器)
    const tx = await program.methods
      .initializeCounter()
      .accounts({
        counter: counterKeypair.publicKey, // 计数器账户地址
        user: wallet.publicKey, // 调用者地址
        systemProgram: anchor.web3.SystemProgram.programId, // 系统程序(创建账户用)
      })
      .signers([counterKeypair]) // 新账户需要签名(因为是刚生成的)
      .rpc(); // 发送交易

    console.log("交易签名:", tx);

    // 4. 验证结果
    const counterAccount = await program.account.counter.fetch(counterKeypair.publicKey);
    console.log("初始计数:", counterAccount.count.toString());
    assert.equal(counterAccount.count.toString(), "0"); // 断言初始值为0
  });
});

2. 测试注意事项

  • 集群选择:默认使用 localnet(本地节点),测试前需启动 solana-test-validator
  • 账户权限
    • 新生成的账户(Keypair.generate())需要放在 signers 中签名
    • 钱包账户(wallet.publicKey)的签名由 provider 自动处理,无需手动添加
  • 数据读取:用 program.account.xxx.fetch(地址) 读取账户数据
  • 错误处理:使用 try/catch 捕获交易失败(如权限不足、数据错误)

3. 常用 TS 语法(测试中高频使用)

  • 类型注解const wallet: anchor.Wallet = provider.wallet;(指定变量类型)
  • 异步函数async/await 处理区块链交易(网络操作都是异步的)
  • 接口定义:描述账户数据结构
    interface Counter {
      count: anchor.BN; // Solana中常用BN处理大数字
      owner: anchor.web3.PublicKey;
    }
  • 解构赋值:快速获取账户字段
    const { count, owner } = await program.account.counter.fetch(addr);

三、前端交互(调用 Anchor 程序)

前端通过 Anchor 客户端库与链上程序交互,核心流程:连接钱包 → 初始化程序 → 调用指令。

1. 前端项目结构(app/ 目录)

app/
├── components/  # UI组件
├── lib/         # 工具函数(连接钱包、程序初始化)
│   └── anchor.ts # 核心交互逻辑
└── pages/       # 页面(如首页)

2. 核心交互代码(lib/anchor.ts

import * as anchor from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import idl from "../target/idl/my_solana_dapp.json"; // 程序IDL(接口描述)

// 1. 初始化程序
export const initProgram = () => {
  // 连接到浏览器钱包(如Phantom)
  const provider = new anchor.AnchorProvider(
    window.solana, // 钱包注入的provider
    window.solana,
    { preflightCommitment: "processed" }
  );
  anchor.setProvider(provider);

  // 加载程序(IDL + 程序地址)
  const programId = new PublicKey(idl.metadata.address);
  const program = new anchor.Program(idl as any, programId, provider);
  return { program, provider };
};

// 2. 调用程序指令(比如增加计数器)
export const incrementCounter = async (counterAddr: string) => {
  const { program, provider } = initProgram();
  const counterPubkey = new PublicKey(counterAddr);

  try {
    // 发送交易
    const tx = await program.methods
      .incrementCounter() // 对应Rust中的increment_counter函数
      .accounts({
        counter: counterPubkey,
        user: provider.wallet.publicKey,
      })
      .rpc(); // 钱包会自动弹出签名请求

    console.log("交易成功:", tx);
    return tx;
  } catch (err) {
    console.error("交易失败:", err);
  }
};

3. 在页面中使用(React 示例)

// pages/index.tsx
import { useState } from "react";
import { incrementCounter } from "../lib/anchor";

export default function Home() {
  const [counterAddr, setCounterAddr] = useState("");

  const handleIncrement = async () => {
    if (!counterAddr) return;
    await incrementCounter(counterAddr);
  };

  return (
    <div>
      <input
        placeholder="计数器地址"
        value={counterAddr}
        onChange={(e) => setCounterAddr(e.target.value)}
      />
      <button onClick={handleIncrement}>增加计数</button>
    </div>
  );
}

4. 前端交互注意事项

  • 钱包连接:需用户安装 Phantom 等钱包,通过 window.solana 注入
  • 权限请求:首次调用会请求钱包授权(访问公钥)
  • 交易确认:区块链交易需要时间确认,可通过 @solana/web3.jsConnection.confirmTransaction 等待确认
  • 错误提示:常见错误如“用户拒绝签名”“余额不足”(需确保钱包有 SOL 支付手续费)

四、快速上手命令

# 启动本地节点
solana-test-validator

# 部署程序到本地节点
anchor deploy

# 运行测试
anchor test

# 启动前端(开发模式)
cd app && npm run dev

总结

重点理解:

  1. 测试文件通过模拟交易验证程序逻辑,核心是 program.methods.xxx 调用指令
  2. 前端交互本质是通过 Anchor 客户端库将用户操作转化为链上交易,依赖钱包签名
  3. TypeScript 类型系统帮助匹配程序接口(IDL),减少调用错误

通过以上流程,可实现从后端程序到前端交互的完整闭环,后续可逐步深入复杂功能(如 NFT 操作、状态管理等)。