正如我之前提到的,编写测试将是 FunC 合约编程中非常重要的一部分。让我们回顾一下上一课创建的测试: 2 Z, y* j) A9 B
- import { Cell, toNano } from "ton-core";$ J4 w z6 O$ Q' G, z" h! w: p0 k
- import { hex } from "../build/main.compiled.json";
& u, g$ O" q$ [3 p" C" \ - import { Blockchain } from "@ton-community/sandbox";& O6 F7 s- c5 t8 D; o0 C1 U
- import { MainContract } from "../wrappers/MainContract";
4 x+ s7 | I& M( ? - import "@ton-community/test-utils";
, M6 m0 z% K4 G" `: h2 y - . B+ d3 C9 i: L) H4 U4 B
- describe("main.fc contract tests", () => {
# W) e' N) p- m" X- @; ~! B) { - it("should get the proper most recent sender address", async () => {
5 F! ]! ^. p6 ~- C( K: I - const blockchain = await Blockchain.create();
0 Z" k/ t( I: D3 ]$ U - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
5 x1 [3 `4 B, R9 i4 B. U4 ~
{; h. I$ m2 Q- D# W7 X4 s N- const myContract = blockchain.openContract(
/ R; w& G7 V/ d! ?! [' e: t - await MainContract.createFromConfig({}, codeCell)
: a+ ?2 H g4 H4 v R- C - );- p4 O7 t1 B, O" `& `- V
. y A. H/ M* e* o3 M* X/ q/ n6 p r- {- const senderWallet = await blockchain.treasury("sender");
, U) ?- W% N8 p1 P - 4 w* Z, S. L: ]( J, u% S
- const sentMessageResult = await myContract.sendInternalMessage(4 P! J% }/ Q+ ?4 E5 m- R1 \6 Z
- senderWallet.getSender(),1 D, u5 g# Y* T0 k. ?( }; R
- toNano("0.05")
, j8 g2 D2 i* G: V: t - );
3 |4 b- T2 C" z
) f" s& i+ t0 y, D) A- expect(sentMessageResult.transactions).toHaveTransaction({4 y! F4 x/ l/ N |5 J. \
- from: senderWallet.address,% B& P( \" m, v o; _2 f. t* S N, M
- to: myContract.address,
- k1 V$ b( w a, ~; c' J0 y8 y - success: true,( n3 ]( p7 a; d s. N$ b
- });! b* @. `1 h3 Q) R) G% M
1 _9 F/ A( R; n3 w, C- const data = await myContract.getData();
$ r. ~! R N0 x, Z
5 n8 ]' ^: E, K" x- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());% c% Q# }: \2 L5 ^; ^
- });0 v) S% W$ f8 n% p, [" z1 i T9 ?
- });
复制代码值得一提的是,如果我们现在运行 yarn 测试,它将无法工作,因为我们的代码发生了很大变化。为了解决这个问题,我们需要做的如下: 让我们来处理初始数据。正如你所记得的,我们使用 createFromConfig 方法来初始化我们的合约,并通过以下方式将合约的初始状态数据设置为简单的空Cell: - static createFromConfig(config: any, code: Cell, workchain = 0) {5 R1 m; G* i- @0 _7 ~( _
- const data = beginCell().endCell();' t5 i5 T# N; F; Z5 T
- const init = { code, data };
5 ]) o4 ?) w6 z5 D8 J& ` - const address = contractAddress(workchain, init);
) H- H, g9 e* Q& x w - ! p! P' Z9 [5 M# [! C
- return new MainContract(address, init);
; V& i! ?( N! A, [9 m2 D* H3 \; P - }
复制代码然后在 main.spec.ts 中调用 createFromConfig: 0 D' Q$ w4 ?1 S0 o, P' c0 u1 i
- const myContract = blockchain.openContract(
2 G' F8 ~( Y4 z0 o - await MainContract.createFromConfig({}, codeCell)- |/ {6 v0 [( x/ O/ m$ ^
- );
复制代码到目前为止,我们传递的是一个空对象作为配置,但现在我们可以传递一些值,这些值实际上是我们初始化合约的适当数据所需要的。 让我们定义一下我们的 config 应该是什么样子。在 wrappers/MainContract.ts 中导入后,我们将定义一个名为 MainContractConfig 的新类型。 - // ... library imports
* i: o4 x5 |2 j C% x
8 k1 Y8 E/ d$ v, w6 `( R7 ^- export type MainContractConfig = {" ?5 ]- B+ g G0 T( H
- number: number;: O' P3 o9 G; x6 X7 x
- address: Address;+ g# B6 K, N5 _" E; I* d/ I
- };
复制代码 我们将在同一封装文件中实现的另一个很酷的辅助函数是 mainContractConfigToCell.它将接收 MainContractConfig 类型的对象,将这些参数正确打包到Cell中并返回.我们需要这样做,因为你还记得,c4 存储空间是一个存储Cell:4 F2 ?* d1 L& \; {) I/ o- h
- // ... library imports
/ e: ~* H& a% `! d - l/ F1 e3 R4 S* J- B, S9 X
- // ... MainContractConfig type8 \$ D& `8 Y; E, E; N8 R& O; t
- / R$ i: F" `+ b3 z# n) A& ?
- export function mainContractConfigToCell(config: MainContractConfig): Cell {/ i2 R# b. X9 ^: V9 V8 T, {" T
- return beginCell().storeUint(config.number, 32).storeAddress(config.address).endCell();
. T* G' o( }$ A7 }' B; N$ f4 ]& w, t - }
复制代码 现在让我们更新同一文件中的 createFromConfig,不过是在 MainContract 类中。现在,它必须使用 mainContractConfigToCell 函数来形成初始状态数据 Cell:
+ I j# m) R" F7 A J% X$ v- // ... library imports
7 M- _, w5 R6 w8 f9 _4 m - // ... MainContractConfig type2 Y& v4 R* E/ ^& W
- // ... mainContractConfigToCell function
& B& @9 T4 y# G F1 \$ K1 ~+ V - + F) B& S8 h# A
- export class MainContract implements Contract {
* V3 { P& }; y! V; Y. ~ - constructor(- K" c0 S+ q+ H7 |
- readonly address: Address,8 t; ?6 X Z( I( X
- readonly init?: { code: Cell; data: Cell }! }+ r: ]* I( C( Y) M7 C6 u
- ) {}/ V0 a. N" R8 d5 m( A* g+ Z7 `
- & g Q) m/ o. w, p* y, m
- static createFromConfig(! d& c* V( r g* _& o
- config: MainContractConfig,% I4 z9 B) e2 o5 d q) `) x
- code: Cell,
: @ s4 U7 v9 `# i - workchain = 0" P7 ]% o5 L& y, ]# h4 q: [
- ) {
8 J- x9 f; W" | - const data = mainContractConfigToCell(config);
+ R# U7 n8 T2 W: u - const init = { code, data };
5 b* ~/ p$ U4 N - const address = contractAddress(workchain, init);! z5 T3 y. ^: {) l- s7 i
5 H& G' g8 Z1 S! _: Y4 A+ V+ p& t) R- return new MainContract(address, init);
0 J) E: h5 l& e# p& B - }( g" I6 B; z) D, X7 J3 M
-
8 o8 N3 C) C; z0 c6 f% O9 Z - // ... rest of the methods* h0 N b4 ~* I# S- o( k R
- }
复制代码现在,我们的 createFromConfig 方法已经准备就绪,让我们更新一下如何在 tests/main.spec.ts 文件中使用该方法:
$ B9 D, L# z% e; n7 D- // ... library imports
: a7 b4 L) K: K: l B& U
, j) p. o7 u+ F3 ^) ?- describe("main.fc contract tests", () => {1 h' X7 l# g1 I; A6 J
- it("should get the proper most recent sender address", async () => {
% e( J& z( n6 k% c, W/ l, M7 { - const blockchain = await Blockchain.create();
: I6 f0 p1 G" O/ M - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
- R+ _- O+ B$ C9 P
3 Q$ @3 p, w" N i* b+ d! b Q3 F- const initAddress = await blockchain.treasury("initAddress");
: K7 S) B4 y) {5 Q3 g: N" A! A
+ y5 c+ A: C" s, Z: A% k9 A0 V- const myContract = blockchain.openContract(5 B: ~- i5 p3 r" b. e
- await MainContract.createFromConfig(
8 ]7 R" f* ], y5 r! A - {
) ^$ q0 ^" O( t0 C- ]9 F# c - number: 0,
" i) T' R4 t/ b9 j) `& N# Y8 r - address: initAddress.address,
; ]3 a2 {4 n: J j" S) N - }," \( Z! E& U6 d& w+ V
- codeCell
. ]" s# x- j! i9 Q4 Y: n - )( {1 L0 k. q1 z( c( G$ ~
- );2 k8 _6 O2 }; m3 \0 u0 l3 A
- 8 A) H( W$ a( D8 \! A+ v6 O) j
- // ... the rest of old tests logic
- J. x8 f j. D+ w, u j - 6 i; K/ _; s% |! o- Q
- }
复制代码请注意我们是如何从助记词 "initAddress "中创建 initAddress 钱包(金库),并将其地址传递到 createFromConfig 我们的 MainContract. 至此,我们成功地初始化了我们的合约!祝贺您 :) 由于我们现在还要在消息正文中发送特定数据--让我们更新方法 sendInternalMessage 在我们的 wrappers/MainContract.ts. 我们将把它重命名为 sendIncrement,现在它还将接受一个值-increment_by。这将是当前合约存储值递增的一个数字。 我们还要记住,这是我们的合约为获取操作码而解析的同一个正文。因此,我们不要忘记首先存储一个 32 位整数(如合约代码所示,1 表示递增),然后再存储一个 32 位整数的递增值: - async sendIncrement($ g0 N' m0 e! Y# @- p+ K7 l
- provider: ContractProvider,
! l i; z/ R# R! E4 _3 x/ h, F+ x( w' i - sender: Sender,+ B; Y% v6 ~1 l# e# X3 z0 S
- value: bigint,
5 l+ r& z: |+ ?" H+ [1 \+ D/ D - increment_by: number
) g2 p, B+ t$ t% u0 ~ - ) {
; r5 S1 p# m0 t - - [: F/ y# \& e1 w
- const msg_body = beginCell(); W' ?6 @( U' \" L
- .storeUint(1, 32) // OP code
( v' t0 h) P2 z - .storeUint(increment_by, 32) // increment_by value, O! U- E" |3 m5 S
- .endCell();
8 L( C- f; o4 s7 d7 z( q - 6 Y; L5 N: q& U% [4 j
- await provider.internal(sender, {
4 w. Z5 r) H( e8 b# H9 B - value,1 i9 m7 f/ ^4 [
- sendMode: SendMode.PAY_GAS_SEPARATELY,
* ?. t2 G$ f+ n& C: J0 z - body: msg_body,
6 D5 Z( Y2 z8 G% }" A; Y) ?; D, f - });
% f3 Y9 {: }) {" j, ] - }
复制代码 我们还需要更新封装器中的一个方法-getData。正如你所记得的,我们重新命名了 getter 方法,而且它现在返回两个值。让我们调整一下 wrapper 方法:4 }8 \. b0 X: a3 i8 e
- async getData(provider: ContractProvider) {
; }. M4 G9 i2 u. J' S - const { stack } = await provider.get("get_contract_storage_data", []);
; \- N* Y7 y1 N5 y V8 ?2 K9 ^9 d - return {( y2 P* B3 ^4 m2 d
- number: stack.readNumber(),+ B5 n5 y+ i- |
- recent_sender: stack.readAddress(),+ C, V* }/ X( t q" N2 _
- };
1 K& p/ _5 L, U9 ^, s* B - }
复制代码请注意,我们是如何读取 getter 方法的堆栈结果的。我们使用 readNumber() 读取当前值的整数。 现在,我们可以返回 tests/main.spec.ts,继续完成测试。以下是测试的更新代码: - // ... library imports
6 Z, z( \8 _. a5 p' `
6 d0 {% J& u8 P- describe("main.fc contract tests", () => {1 Q0 o! f( x' k; e L* O2 ~/ J: \! t
- it("should successfully increase counter in contract and get the proper most recent sender address", async () => {. _% M! ` [! |
- const blockchain = await Blockchain.create();
9 d+ A i% h4 n9 }* v - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
4 O' i1 E2 G- Z: h - 2 {/ n9 Q- [1 `9 |/ X t& F
- const initAddress = await blockchain.treasury("initAddress");
& u: J' y. U7 ], m, j! h
$ _7 @$ D1 d6 @. v8 D: m* @+ a- const myContract = blockchain.openContract(
4 P8 _; W; r( I d$ W1 R* N - await MainContract.createFromConfig(
, ^; p0 {7 u% _4 n2 C - {$ q1 ^& }, [5 D# f! v
- number: 0,
* y* X6 c+ e. O - address: initAddress.address,$ Z0 M% ~: T% d, z
- }," r& G5 P* _& h7 \7 x
- codeCell$ A. C% P" O+ q+ ~. K& b
- )
* p F$ z5 a% R, p3 o, i# |! z" ? - );
/ j* G, v/ M! v5 H% Q4 r& L - 8 E8 W5 O5 B0 i& c9 t; K& T7 ^
- const senderWallet = await blockchain.treasury("sender");
9 A& Q( G9 }+ i& _/ {' S
- N1 i+ t N! I, b7 C2 T/ Q5 g- const sentMessageResult = await myContract.sendIncrement(
- f9 o; K' X0 Y$ ^- u - senderWallet.getSender(),
1 M# L8 q5 @% i4 n1 h - toNano("0.05"),
3 x4 X F0 \# R) x7 K - 1! ?' S x' P$ j+ v( U, v' d0 n( _
- );% E8 X/ s8 K! O- j
- / i" B5 N* d9 u6 Q% c
- expect(sentMessageResult.transactions).toHaveTransaction({8 \1 c* A* K5 g L
- from: senderWallet.address,: B3 @" f/ o" h
- to: myContract.address,5 h0 r* C8 z1 Y
- success: true,, S+ K9 L4 Z, E* q6 Q. r$ S# g
- });- [/ @! w4 X( {2 r6 q
- ; Z3 e7 ]! j2 G: C7 D" R' ?
- const data = await myContract.getData();9 [2 e; C3 B. i9 F+ G( q% c) |5 Y
- & o2 {1 H3 A3 e; E! p2 J' k
- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());5 c# R1 L1 S$ [0 L
- expect(data.number).toEqual(1);
) f5 L, g5 q" P: h+ V: ? - });4 a" D8 j& u. ]& K s+ ^) W, T2 ~1 h
- });
复制代码我们在这里更新了什么? 现在我们可以使用 yarn test 命令。如果一切操作正确,你将在终端中看到以下内容: - PASS tests/main.spec.ts
* e* j0 b4 P. g8 T - main.fc contract tests4 H2 |# M7 v- J+ d; A
- ✓ should successfully increase counter in contract and get the proper most recent sender address (452 ms)+ c. Z$ }; Z% y, R7 ?% d' ?
, W8 H* Z! P. `8 j% c. a4 F- Test Suites: 1 passed, 1 total% u* ^3 ]* U1 e7 S+ t
- Tests: 1 passed, 1 total6 s: _6 e J! U; U
- Snapshots: 0 total
6 m0 |! `/ G9 ^: I& D6 w - Time: 4.339 s, estimated 5 s
复制代码 8 n8 r" D: S$ c
. l9 U& f7 m: f; W [7 P R |