正如我之前提到的,编写测试将是 FunC 合约编程中非常重要的一部分。让我们回顾一下上一课创建的测试:
. x; B& a9 ^& _$ J/ z, q' H- import { Cell, toNano } from "ton-core";
3 n8 g: _6 _1 J5 X# N3 R% T - import { hex } from "../build/main.compiled.json";
4 R0 \6 c$ v0 ]0 W - import { Blockchain } from "@ton-community/sandbox";3 I$ j4 Z. e" g f; P; C
- import { MainContract } from "../wrappers/MainContract";
$ ?3 v- ~! O& ~' Y - import "@ton-community/test-utils"; |) I. q* E' |. x. [( W# T: t! r
) P) R6 Z' N( [; n# W- describe("main.fc contract tests", () => {, |! e- c5 m+ O! ~! R
- it("should get the proper most recent sender address", async () => {
s" Q1 ?8 n k+ m - const blockchain = await Blockchain.create();) _! @" h# T6 K
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];9 b$ R. ^, c& K: k8 V5 L3 s# A
^; L1 w9 g5 `- const myContract = blockchain.openContract(
0 F0 v. z% W; ?; T0 y( Y& w" @ - await MainContract.createFromConfig({}, codeCell). v1 p! f, C6 H5 i
- );
& P( P: a1 i* B7 H; y
7 d+ ^# V0 L( P( H8 d4 e- const senderWallet = await blockchain.treasury("sender");
$ f# c2 L( x d h - 4 f) i" p( C' X
- const sentMessageResult = await myContract.sendInternalMessage(9 z; k. h6 n- {% D4 R
- senderWallet.getSender(),
$ `, {! b7 t8 B1 F& B: @* y* C - toNano("0.05")
& ]) W7 `; n6 g! B& w7 p5 y! j - );
! s, G5 t0 o; m" z& _+ ]8 D: v2 `: k - 5 m7 x+ U2 e2 n- V% u) }
- expect(sentMessageResult.transactions).toHaveTransaction({( E- H+ K( }! p$ o- _
- from: senderWallet.address,
' A, {; l- F5 @8 e7 `1 V - to: myContract.address,
3 F& B U' g Y M9 L2 ?/ f; x( W - success: true,
$ A. }+ `( j R# ^6 H - });
# _' j5 e+ p& } s& @: S6 P
+ c( T) J4 u ^8 r- const data = await myContract.getData();
+ R: [& y' z% |/ X8 V
7 G" x8 D8 C! b- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());2 H+ S7 p& A Q# X4 x# ^
- });
* W0 Q0 _3 ~+ S! O/ Q& V I - });
复制代码值得一提的是,如果我们现在运行 yarn 测试,它将无法工作,因为我们的代码发生了很大变化。为了解决这个问题,我们需要做的如下: 让我们来处理初始数据。正如你所记得的,我们使用 createFromConfig 方法来初始化我们的合约,并通过以下方式将合约的初始状态数据设置为简单的空Cell: - static createFromConfig(config: any, code: Cell, workchain = 0) {
* Z" j- H' Z6 j - const data = beginCell().endCell();7 _# k+ o0 {( t
- const init = { code, data };2 s7 e6 s: E" K) R+ s
- const address = contractAddress(workchain, init);! Y, Q& s; a3 t- [0 T( F5 k8 x
- 0 q4 @1 ~; c, F# q6 ?+ ~; X1 u! @
- return new MainContract(address, init);+ [6 K+ c, K2 m2 R/ P
- }
复制代码然后在 main.spec.ts 中调用 createFromConfig:
5 m; P8 R: o$ A- const myContract = blockchain.openContract(
$ B( x' G. F1 e7 P - await MainContract.createFromConfig({}, codeCell)
9 ?! z; n5 i; ^; q3 V - );
复制代码到目前为止,我们传递的是一个空对象作为配置,但现在我们可以传递一些值,这些值实际上是我们初始化合约的适当数据所需要的。 让我们定义一下我们的 config 应该是什么样子。在 wrappers/MainContract.ts 中导入后,我们将定义一个名为 MainContractConfig 的新类型。 - // ... library imports
" G( {- m+ U; v6 ^* f. X/ u# d
# E! {0 V; G. Q! p$ _- export type MainContractConfig = {
- s, E i1 t; m+ U' f5 G$ n - number: number;3 b6 I+ `" B$ v. z1 g/ i
- address: Address;2 j) s* e* N, w6 L7 e0 h
- };
复制代码 我们将在同一封装文件中实现的另一个很酷的辅助函数是 mainContractConfigToCell.它将接收 MainContractConfig 类型的对象,将这些参数正确打包到Cell中并返回.我们需要这样做,因为你还记得,c4 存储空间是一个存储Cell:
1 z6 h- P3 \1 I- ~$ ~0 u- // ... library imports3 |6 s$ L. n0 P4 u- s
2 K. r1 M5 M* ?- // ... MainContractConfig type
6 b& t" S6 ~( y$ k9 [; L+ Z
# V) j% L0 i4 q' h- export function mainContractConfigToCell(config: MainContractConfig): Cell {+ w1 m% ^% O/ P0 l0 @' K5 V4 M
- return beginCell().storeUint(config.number, 32).storeAddress(config.address).endCell(); I( W0 t9 C& y) N1 \+ J
- }
复制代码 现在让我们更新同一文件中的 createFromConfig,不过是在 MainContract 类中。现在,它必须使用 mainContractConfigToCell 函数来形成初始状态数据 Cell:: @5 Y1 ~4 b/ _" A7 ~# o
- // ... library imports
6 I' {3 L2 U) y/ E7 \- B - // ... MainContractConfig type
/ _6 Z O2 ^4 n, F6 B1 v - // ... mainContractConfigToCell function0 Z; R( W1 |& `1 e
- % q A3 w8 [0 C
- export class MainContract implements Contract {
! T& `( Z$ Z( o4 ^, K5 ^+ N4 n; k - constructor(
% K, x3 ?; p0 P8 A) ^1 f - readonly address: Address,( v, Z6 ]9 D% |& X; x# |/ ]; M9 ?
- readonly init?: { code: Cell; data: Cell }. U* ^* G' l/ s8 P9 b" L
- ) {}0 k" O. \2 _) W0 p O
) ]$ K3 w/ h8 H% r- static createFromConfig(* E# u, S. f- ^
- config: MainContractConfig,
) z$ O$ C" M, s$ D c - code: Cell,
9 d6 u% w; P9 v - workchain = 0# c( u# }# j5 Y) n0 E) ^
- ) {0 L+ o' w# @5 t3 `5 g
- const data = mainContractConfigToCell(config);; h/ s4 _% g7 c. D; b: {! g
- const init = { code, data };( J5 j$ x) t# O' v
- const address = contractAddress(workchain, init);
5 n/ L1 _7 S9 b7 N9 e# d/ R - 5 x$ n) a' P+ J2 M/ u+ Q
- return new MainContract(address, init);8 `/ L+ |+ d& \$ O* t; u+ n2 J
- }
8 |! k& m! `3 r# b5 J, @ y) g9 M - 5 |, @2 U+ b; v& I) s& N3 z5 o
- // ... rest of the methods9 D$ \' P$ D7 X9 I9 P3 ]6 l' \
- }
复制代码现在,我们的 createFromConfig 方法已经准备就绪,让我们更新一下如何在 tests/main.spec.ts 文件中使用该方法:
1 r g; w- b0 T+ O0 ?- // ... library imports
7 F% C. V& J( p1 T - , m# k1 E" d& N3 _+ n
- describe("main.fc contract tests", () => {
2 t6 r H4 x. Q( K" D8 }) h) n! c$ |: W - it("should get the proper most recent sender address", async () => {
: {: e8 N- D) a8 |( `/ q; l - const blockchain = await Blockchain.create();$ c9 u1 p: G+ @* l
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];; O; x) @' A) Y9 z, {+ P" h5 W
- + B( E0 I6 Z1 S: @5 C/ q
- const initAddress = await blockchain.treasury("initAddress");; G/ E% k% P" ^, o: n2 U$ o) g
- # j* \4 D1 J' Y/ ?
- const myContract = blockchain.openContract(. w9 g5 P/ B/ W; }& L d" N
- await MainContract.createFromConfig(
; o6 g' i8 j" |+ W+ r3 i* K - {/ H$ ]3 A' Z& S: f
- number: 0,; W0 y0 S- I! ?5 c/ x
- address: initAddress.address,7 K, E8 ?4 L2 l! m: `* Y
- },
) {6 G# f% L3 i% G' s - codeCell
0 w! L& `; x% E2 H: D) g: n - )
: \2 c2 P4 C# M% l& c7 s - );% ?* Y4 C7 M3 e3 @0 Z/ i
- / a+ h5 z' P8 J; W4 L# [( E
- // ... the rest of old tests logic4 T& O, o; X# |
- - |/ k% \& C$ ~
- }
复制代码请注意我们是如何从助记词 "initAddress "中创建 initAddress 钱包(金库),并将其地址传递到 createFromConfig 我们的 MainContract. 至此,我们成功地初始化了我们的合约!祝贺您 :) 由于我们现在还要在消息正文中发送特定数据--让我们更新方法 sendInternalMessage 在我们的 wrappers/MainContract.ts. 我们将把它重命名为 sendIncrement,现在它还将接受一个值-increment_by。这将是当前合约存储值递增的一个数字。 我们还要记住,这是我们的合约为获取操作码而解析的同一个正文。因此,我们不要忘记首先存储一个 32 位整数(如合约代码所示,1 表示递增),然后再存储一个 32 位整数的递增值: - async sendIncrement(
7 g, x$ h. f$ u& { x J - provider: ContractProvider,
" U7 p2 f& A# y( d% V - sender: Sender,: D8 u d4 N. B% n$ v
- value: bigint,* o/ g9 _2 e9 ^8 y4 f8 y
- increment_by: number* N: y3 t! f" K$ {3 J. ~7 }$ H6 O6 k9 o
- ) {
/ h, @& n. o7 _1 d -
9 G1 `( a+ k0 e4 D9 a - const msg_body = beginCell()
9 ~/ {! w1 p$ n: S8 C, L - .storeUint(1, 32) // OP code
, V! E, X- C8 G( B% `/ C - .storeUint(increment_by, 32) // increment_by value
3 ]. \7 K( P! _. g% s - .endCell();+ i6 |$ T S: D Q9 F+ K4 D% p
- % O' D& P( x. g! k! W1 s
- await provider.internal(sender, {. O+ W8 E i2 w- _ I" N% i0 x
- value,
; [, j$ F3 O$ m; c! E$ N; t! M - sendMode: SendMode.PAY_GAS_SEPARATELY,+ H. x+ @$ l+ K9 v
- body: msg_body,
! S% ^% H0 D/ l - });# I2 w, t: @. L% \4 C
- }
复制代码 我们还需要更新封装器中的一个方法-getData。正如你所记得的,我们重新命名了 getter 方法,而且它现在返回两个值。让我们调整一下 wrapper 方法:
k1 G9 Q2 h* S' k- async getData(provider: ContractProvider) {
' p6 n' [7 L3 i1 D# }4 L+ W* z - const { stack } = await provider.get("get_contract_storage_data", []);
4 h9 b; |+ ]3 n( Y+ W+ U; Q, f - return {
, {# e9 E J8 Y3 C1 ~5 \ - number: stack.readNumber(),
% v- g& O: f& {7 G6 |; A - recent_sender: stack.readAddress(),
# f9 X9 s: d/ A/ }( f - };# ^; i N& Z! m! `
- }
复制代码请注意,我们是如何读取 getter 方法的堆栈结果的。我们使用 readNumber() 读取当前值的整数。 现在,我们可以返回 tests/main.spec.ts,继续完成测试。以下是测试的更新代码: - // ... library imports
- O4 K; E# J) L* Z# t2 i% M
1 @& h" c$ _% a- describe("main.fc contract tests", () => {
/ Y" r7 d4 g* Q$ E - it("should successfully increase counter in contract and get the proper most recent sender address", async () => {
) D" m3 E3 L* f$ U' E. a+ z4 T% n - const blockchain = await Blockchain.create();
' Y5 {* Y2 G& F! k7 R! x - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
. _# R3 H4 ?4 ]0 k
" O% v) Z" m# s R, X% D* X- const initAddress = await blockchain.treasury("initAddress");3 k& Y/ a9 b! t# D% R6 s
, R% F2 g/ G" B; G: M& |9 V- const myContract = blockchain.openContract(
, H3 [- e- E9 N; T - await MainContract.createFromConfig(
! V4 N# R- g3 f; @ - {
8 X! T& C) T e- t - number: 0,; u' W# V% O/ Y4 b8 f* f
- address: initAddress.address,1 v/ y" j: U$ J& Y8 M. v7 H' b
- },1 U& y* g$ y5 n+ T- f
- codeCell
$ y- w0 ]; N; L% Z - )7 O% u- V# b" O
- );: k9 }' Q3 A( [3 V1 A( z' P6 }- k
1 s3 a, n. v! U2 S0 d1 N _+ N- const senderWallet = await blockchain.treasury("sender");
v4 G# d. Y$ i2 \1 e, j - 0 w p% ^, |5 I9 ]& a( m; Y* |
- const sentMessageResult = await myContract.sendIncrement(- Q1 N( G& W& k/ B
- senderWallet.getSender(),' j3 W" @, g- Z5 T2 G6 N& I" w
- toNano("0.05"),* `4 r/ E# O) }
- 1
/ B( Z5 G5 B* n - );
1 n& {$ |1 Q& ~: q2 u: o% O' n, ^ - . a: [1 e& S$ f" c- G) C
- expect(sentMessageResult.transactions).toHaveTransaction({2 e; s/ @% u3 J& e! b. {7 h. |0 s
- from: senderWallet.address,
/ E/ I5 U6 c: f$ I0 G% E' S - to: myContract.address,
/ {- x* M* n, h8 [- Z - success: true,1 e3 E) p n4 w. A
- });
( {% O( Z: d7 L5 L
. ^5 l8 f9 N3 `* P$ ]- const data = await myContract.getData();
2 g0 s6 |1 w9 R7 y
! Y. C" r" ~. z. f5 ~9 F+ W- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());
- | j) L, z+ p X1 A0 w2 b - expect(data.number).toEqual(1);4 i0 s( A5 Y9 V- p; X% |: i+ A
- });
: c- o& ~5 y; ~2 H' o - });
复制代码我们在这里更新了什么? 现在我们可以使用 yarn test 命令。如果一切操作正确,你将在终端中看到以下内容: - PASS tests/main.spec.ts
1 P7 {. H+ F9 G9 H - main.fc contract tests
% U$ i8 B7 M& }# _ - ✓ should successfully increase counter in contract and get the proper most recent sender address (452 ms)% J! {' f3 @! d8 m' E% X
- N- A. c0 B [ T) w
- Test Suites: 1 passed, 1 total
( U. b5 @4 _2 R6 \! t - Tests: 1 passed, 1 total+ p; I6 A" ~. z: U- A7 n) V4 N
- Snapshots: 0 total3 L! A* h- i$ ?* A; \1 c
- Time: 4.339 s, estimated 5 s
复制代码
: C. @" F% C6 B& ~4 T
( n$ o5 n& e: X+ z |