28 minutes
Cadre pour les tests unitaires
Apprenez à utiliser Matchstick, un cadre de test unitaire développé par LimeChain. Matchstick permet aux développeurs de subgraphs de tester leur logique de mappages dans un environnement sandbox et de déployer avec succès leurs subgraphs.
Avantages de l’utilisation de Matchstick
- Il est écrit en Rust et optimisé pour des hautes performances.
- Il vous donne accès à des fonctions de développement, notamment la possibilité de simuler des appels de contrat, de faire des assertions sur l’état du store, de surveiller les échecs du subgraph, de vérifier les performances des tests, et bien d’autres choses encore.
Introduction
Installation des dépendances
Pour utiliser les méthodes d’aide aux tests et exécuter les tests, vous devez installer les dépendances suivantes :
1yarn add --dev matchstick-as
Installer PostgreSQL
graph-node
dépend de PostgreSQL, donc si vous ne l’avez pas déjà, vous devrez l’installer.
Remarque : Il est fortement recommandé d’utiliser les commandes ci-dessous pour éviter les erreurs inattendues.
En utilisant MacOS
Commande d’installation :
1brew install postgresql
Créez un lien symbolique vers la dernière version de libpq.5.lib Vous devrez peut-être créer ce répertoire d’abord /usr/local/opt/postgresql/lib/
1ln -sf /usr/local/opt/postgresql@14/lib/postgresql@14/libpq.5.dylib /usr/local/opt/postgresql/lib/libpq.5.dylib
En utilisant Linux
Commande d’installation (dépend de votre distribution) :
1sudo apt installer postgresql
En utilisant WSL (Windows Subsystem for Linux)
Vous pouvez utiliser Matchstick sur WSL en utilisant à la fois l’approche Docker et l’approche binaire. Comme WSL peut être un peu délicat, voici quelques conseils au cas où vous rencontreriez des problèmes tels que
1static BYTES = Symbol("Bytes") SyntaxError: Unexpected token =
ou bien
1<PROJECT_PATH>/node_modules/gluegun/build/index.js:13 throw up;
Veuillez vous assurer que vous êtes sur une version plus récente de Node.js graph-cli ne prend plus en charge v10.19.0, et c’est toujours la version par défaut pour les nouvelles images Ubuntu sur le WSL. Par exemple, il est confirmé que Matchstick fonctionne sur WSL avec v18.1.0, vous pouvez passer à cette version via nvm ou si vous mettez à jour votre Node.js global. N’oubliez pas de supprimer node_modules
et de relancer npm install
après avoir mis à jour votre nodejs ! Ensuite, assurez-vous que libpq est installé, vous pouvez le faire en exécutant
1sudo apt-get install libpq-dev
Et enfin, n’utilisez pas graph test
(qui utilise votre installation globale de graph-cli et pour une raison quelconque, il semble qu’il soit cassé sur WSL actuellement), utilisez plutôt yarn test
ou npm run test
(qui utilisera l’instance locale, au niveau du projet, de graph-cli, ce qui fonctionne comme un charme). Pour cela, vous devez bien sûr avoir un script " test "
dans votre fichier package.json
qui peut être quelque chose d’aussi simple que
1{2 "name": "demo-subgraph",3 "version": "0.1.0",4 "scripts": {5 "test": "graph test",6 ...7 },8 "dependencies": {9 "@graphprotocol/graph-cli": "^0.56.0",10 "@graphprotocol/graph-ts": "^0.31.0",11 "matchstick-as": "^0.6.0"12 }13}
En utilisant Matchstick
Pour utiliser Matchstick dans votre projet Subgraph, ouvrez simplement un terminal, naviguez jusqu’au dossier racine de votre projet et lancez simplement graph test [options] <datasource>
- il télécharge le dernier binaire Matchstick et exécute le test spécifié ou tous les tests dans un dossier de test (ou tous les tests existants si aucun flag de source de données n’est spécifié).
CLI options
Cette opération permet d’exécuter tous les tests contenus dans le dossier test :
1graph test
Ceci lancera un test nommé gravity.test.ts et/ou tous les tests à l’intérieur d’un dossier nommé gravity :
1gravity graph test
Ce fichier de test sera le seul à être exécuté :
1graph test path/to/file.test.ts
Options:
1-c, --coverage Exécute les tests en mode couverture2-d, --docker Exécute les tests dans un conteneur Docker (Note : Exécute à partir du dossier racine du subgraph).3-f, --force Binaire : Redécharge le binaire. Docker : Redécharge le fichier Docker et reconstruit l'image Docker.4-h, --help Affiche les informations sur l'utilisation5-l, --logs Enregistre dans la console des informations sur le système d'exploitation, le modèle de processeur et l'adresse de téléchargement (à des fins de débogage).6-r, --recompile Oblige à recompiler les tests7-v, --version <tag> Choisi la version du binaire rust que vous souhaitez télécharger/utiliser
Docker
Depuis graph-cli 0.25.2
, la commande graph test
supporte l’exécution de matchstick
dans un conteneur docker avec l’option -d
. L’implémentation de docker utilise bind mount pour ne pas avoir à reconstruire l’image de docker à chaque fois que la commande graph test -d
est exécutée. Vous pouvez également suivre les instructions du dépôt matchstick pour exécuter docker manuellement.
❗ graph test -d
force docker run
à s’exécuter avec le flag -t
. Ceci doit être supprimé pour fonctionner dans des environnements non-interactifs (comme GitHub CI).
❗ Si vous avez précédemment exécuté graph test
, vous pouvez rencontrer l’erreur suivante lors du build de docker :
1error from sender: failed to xattr node_modules/binary-install-raw/bin/binary-<platform>: permission denied
Dans ce cas, créez un .dockerignore
dans le dossier racine et ajoutez node_modules/binary-install-raw/bin
La Configuration
Matchstick peut être configuré pour utiliser un chemin personnalisé pour les tests, les librairies et les manifestes via le fichier de configuration matchstick.yaml
:
1testsFolder: path/to/tests2libsFolder: path/to/libs3manifestPath: path/to/subgraph.yaml
Subgraph Demo
Vous pouvez essayer et jouer avec les exemples de ce guide en clonant le dépôt du Demo Subgraph.
Tutoriels vidéos
Vous pouvez également consulter la série de vidéos sur “Comment utiliser Matchstick pour écrire des tests unitaires pour vos subgraphs”
Structure des tests
IMPORTANT : La structure de test décrite ci-dessous dépend de la version de matchstick-as
>=0.5.0
décrivez()
describe(name : String , () => {})
- Définit un groupe de test.
Notes:
- Les descriptions ne sont pas obligatoires. Vous pouvez toujours utiliser test() à l’ancienne, en dehors des blocs describe()
L’exemple:
1import { describe, test } from "matchstick-as/assembly/index"2import { handleNewGravatar } from "../../src/gravity"34describe("handleNewGravatar()", () => {5 test("Il faut créer une nouvelle entité Gravatar", () => {6 ...7 })8})
Exemple imbriqué de describe()
:
1import { describe, test } from "matchstick-as/assembly/index"2import { handleUpdatedGravatar } from "../../src/gravity"34describe("handleUpdatedGravatar()", () => {5 describe("Lorsque l'entité existe", () => {6 test("met à jour l'entité", () => {7 ...8 })9 })1011 describe("Lorsque l'entité n'existe pas", () => {12 test("il crée une nouvelle entité", () => {13 ...14 })15 })16})
tester ()
test(name : String, () =>, should_fail : bool)
- Définit un cas de test. Vous pouvez utiliser test() à l’intérieur des blocs describe() ou indépendamment.
L’exemple:
1import { describe, test } from "matchstick-as/assembly/index"2import { handleNewGravatar } from "../../src/gravity"34describe("handleNewGravatar()", () => {5 test("Doit créer une nouvelle entité", () => {6 ...7 })8})
ou bien
1test("handleNewGravatar() devrait créer une nouvelle entité", () => {2 ...3})
avantTout()
Exécute un bloc de code avant tous les tests du fichier. Si beforeAll
est déclaré à l’intérieur d’un bloc describe
, il s’exécute au début de ce bloc describe
.
Les Exemples:
Le code contenu dans beforeAll
s’exécutera une fois avant tous les tests du fichier.
1import { describe, test, beforeAll } from "matchstick-as/assembly/index"2import { handleUpdatedGravatar, handleNewGravatar } from "../../src/gravity"3import { Gravatar } from "../../generated/schema"45beforeAll(() => {6 let gravatar = new Gravatar("0x0")7 gravatar.displayName = “First Gravatar”8 gravatar.save()9 ...10})1112describe("Lorsque l'entité n'existe pas", () => {13 test("il devrait créer un nouveau Gravatar avec l'identifiant 0x1", () => {14 ...15 })16})1718describe("Lorsque l'entité existe déjà", () => {19 test("il devrait mettre à jour le Gravatar avec l'identifiant 0x0", () => {20 ...21 })22})
Le code contenu dans beforeAll
sera exécuté une fois avant tous les tests du premier bloc de description
1import { describe, test, beforeAll } from "matchstick-as/assembly/index"2import { handleUpdatedGravatar, handleNewGravatar } from "../../src/gravity"3import { Gravatar } from "../../generated/schema"45describe("handleUpdatedGravatar()", () => {6 beforeAll(() => {7 let gravatar = new Gravatar("0x0")8 gravatar.displayName = “First Gravatar”9 gravatar.save()10 ...11 })1213 test("met à jour le Gravatar avec l'identifiant 0x0", () => {14 ...15 })1617 test("crée un nouveau Gravatar avec l'identifiant 0x1", () => {18 ...19 })20})
afterAll()
Exécute un bloc de code après tous les tests du fichier. Si afterAll
est déclaré à l’intérieur d’un bloc describe
, il s’exécute à la fin de ce bloc describe
.
L’exemple:
Le code contenu dans afterAll
sera exécuté une fois après tous les tests du fichier.
1import { describe, test, afterAll } from "matchstick-as/assembly/index"2import { handleUpdatedGravatar, handleNewGravatar } from "../../src/gravity"3import { store } from "@graphprotocol/graph-ts"45afterAll(() => {6 store.remove("Gravatar", "0x0")7 ...8})910describe("handleNewGravatar, () => {11 test("creates Gravatar with id 0x0", () => {12 ...13 })14})1516describe("handleUpdatedGravatar", () => {17 test("updates Gravatar with id 0x0", () => {18 ...19 })20})
Le code contenu dans afterAll
sera exécuté une fois après tous les tests du premier bloc de description
1import { describe, test, afterAll, clearStore } from "matchstick-as/assembly/index"2import { handleUpdatedGravatar, handleNewGravatar } from "../../src/gravity"34describe("handleNewGravatar", () => {5 afterAll(() => {6 store.remove("Gravatar", "0x1")7 ...8 })910 test("Il crée une nouvelle entité avec l'identifiant 0x0.", () => {11 ...12 })1314 test("Il crée une nouvelle entité avec l'identifiant 0x1", () => {15 ...16 })17})1819describe("handleUpdatedGravatar", () => {20 test("updates Gravatar with id 0x0", () => {21 ...22 })23})
beforeEach()
Exécute un bloc de code avant chaque test. Si beforeEach
est déclaré à l’intérieur d’un bloc describe
, il s’exécute avant chaque test dans ce bloc describe
.
Exemples : Le code contenu dans beforeEach
sera exécuté avant chaque test.
1import { describe, test, beforeEach, clearStore } from "matchstick-as/assembly/index"2import { handleNewGravatars } from "./utils"34beforeEach(() => {5 clearStore() // <-- nettoye le store avant chaque test dans le fichier6})78describe("handleNewGravatars, () => {9 test("Un test qui nécessite un store propre", () => {10 ...11 })1213 test("Second, qui nécessite un store propre", () => {14 ...15 })16})1718 ...
Le code contenu dans beforeEach
ne s’exécutera qu’avant chaque test de la description
1import { describe, test, beforeEach } from 'matchstick-as/assembly/index'2import { handleUpdatedGravatar, handleNewGravatar } from '../../src/gravity'34describe('handleUpdatedGravatars', () => {5 beforeEach(() => {6 let gravatar = new Gravatar('0x0')7 gravatar.displayName = 'First Gravatar'8 gravatar.imageUrl = ''9 gravatar.save()10 })1112 test('Met à jour le nom d'affichage (displayName)', () => {13 assert.fieldEquals('Gravatar', '0x0', 'displayName', 'First Gravatar')1415 // code qui devrait mettre à jour le nom d'affichage (displayName) pour le 1er Gravatar1617 assert.fieldEquals('Gravatar', '0x0', 'displayName', '1st Gravatar')18 store.remove('Gravatar', '0x0')19 })2021 test('Met à jour l'imageUrl', () => {22 assert.fieldEquals('Gravatar', '0x0', 'imageUrl', '')2324 // code qui devrait changer l'imageUrl en https://www.gravatar.com/avatar/0x02526 assert.fieldEquals('Gravatar', '0x0', 'imageUrl', 'https://www.gravatar.com/avatar/0x0')27 store.remove('Gravatar', '0x0')28 })29})
afterEach()
Exécute un bloc de code après chaque test. Si afterEach
est déclaré à l’intérieur d’un bloc describe
, il s’exécute après chaque test dans ce bloc describe
.
Les Exemples:
Le code contenu dans afterEach
sera exécuté après chaque test.
1import { describe, test, beforeEach, afterEach } from "matchstick-as/assembly/index"2import { handleUpdatedGravatar, handleNewGravatar } from "../../src/gravity"34beforeEach(() => {5 let gravatar = new Gravatar("0x0")6 gravatar.displayName = “First Gravatar”7 gravatar.save()8})910afterEach(() => {11 store.remove("Gravatar", "0x0")12})1314describe("handleNewGravatar", () => {15 ...16})1718describe("handleUpdatedGravatar", () => {19 test("Met à jour le nom d'affichage (displayName)", () => {20 assert.fieldEquals("Gravatar", "0x0", "displayName", "First Gravatar")2122 // code qui devrait mettre à jour le nom d'affichage (displayName) pour le 1er Gravatar2324 assert.fieldEquals("Gravatar", "0x0", "displayName", "1st Gravatar")25 })2627 test("Met à jour l'imageUrl", () => {28 assert.fieldEquals("Gravatar", "0x0", "imageUrl", "")2930 // code qui devrait changer l'imageUrl en https://www.gravatar.com/avatar/0x03132 assert.fieldEquals("Gravatar", "0x0", "imageUrl", "https://www.gravatar.com/avatar/0x0")33 })34})
Le code contenu dans afterEach
sera exécuté après chaque test de cette description
1import { describe, test, beforeEach, afterEach } from "matchstick-as/assembly/index"2import { handleUpdatedGravatar, handleNewGravatar } from "../../src/gravity"34describe("handleNewGravatar", () => {5 ...6})78describe("handleUpdatedGravatar", () => {9 beforeEach(() => {10 let gravatar = new Gravatar("0x0")11 gravatar.displayName = "First Gravatar"12 gravatar.imageUrl = ""13 gravatar.save()14 })1516 afterEach(() => {17 store.remove("Gravatar", "0x0")18 })1920 test("Met à jour le nom d'affichage (displayName)", () => {21 assert.fieldEquals("Gravatar", "0x0", "displayName", "First Gravatar")2223 // code qui devrait mettre à jour le nom d'affichage (displayName) pour le 1er Gravatar2425 assert.fieldEquals("Gravatar", "0x0", "displayName", "1st Gravatar")26 })2728 test("Met à jour l'imageUrl", () => {29 assert.fieldEquals("Gravatar", "0x0", "imageUrl", "")3031 // code qui devrait changer l'imageUrl en https://www.gravatar.com/avatar/0x03233 assert.fieldEquals("Gravatar", "0x0", "imageUrl", "https://www.gravatar.com/avatar/0x0")34 })35})
Assertions
1fieldEquals(entityType: string, id: string, fieldName: string, expectedVal: string)23equals(expected: ethereum.Value, actual: ethereum.Value)45notInStore(entityType: string, id: string)67addressEquals(address1: Address, address2: Address)89bytesEquals(bytes1: Bytes, bytes2: Bytes)1011i32Equals(number1: i32, number2: i32)1213bigIntEquals(bigInt1: BigInt, bigInt2: BigInt)1415booleanEquals(bool1: boolean, bool2: boolean)1617stringEquals(string1: string, string2: string)1819arrayEquals(array1: Array<ethereum.Value>, array2: Array<ethereum.Value>)2021tupleEquals(tuple1: ethereum.Tuple, tuple2: ethereum.Tuple)2223assertTrue(value: boolean)2425assertNull<T>(value: T)2627assertNotNull<T>(value: T)2829entityCount(entityType: string, expectedCount: i32)
À partir de la version 0.6.0, les assertions supportent également les messages d’erreur personnalisés
1assert.fieldEquals('Gravatar', '0x123', 'id', '0x123', 'Id doit être 0x123')2assert.equals(ethereum.Value.fromI32(1), ethereum.Value.fromI32(1), 'La valeur doit être égale à 1')3assert.notInStore('Gravatar', '0x124', 'Gravatar ne devrait pas être dans le store')4assert.addressEquals(Address.zero(), Address.zero(), 'L'adresse doit être zéro')5assert.bytesEquals(Bytes.fromUTF8('0x123'), Bytes.fromUTF8('0x123'), 'Les Bytes doivent être égaux')6assert.i32Equals(2, 2, 'I32 doit être égal à 2')7assert.bigIntEquals(BigInt.fromI32(1), BigInt.fromI32(1), 'BigInt doit être égal à 1')8assert.booleanEquals(true, true, 'Le booléen doit être vrai')9assert.stringEquals('1', '1', 'La Chaîne de caractère doit être égale à 1')10assert.arrayEquals([ethereum.Value.fromI32(1)], [ethereum.Value.fromI32(1)], 'Les tableaux doivent être égaux')11assert.tupleEquals(12 changetype<ethereum.Tuple>([ethereum.Value.fromI32(1)]),13 changetype<ethereum.Tuple>([ethereum.Value.fromI32(1)]),14 'Les tuples doivent être égaux',15)16assert.assertTrue(true, 'Devrait être vrai')17assert.assertNull(null, 'Devrait être null')18assert.assertNotNull('not null', 'Doit être non null')19assert.entityCount('Gravatar', 1, 'Il devrait y avoir 2 gravatars')20assert.dataSourceCount('GraphTokenLockWallet', 1, 'Le modèle GraphTokenLockWallet doit avoir une source de données')21assert.dataSourceExists(22 'GraphTokenLockWallet',23 Adresse.zero().toHexString(),24 'GraphTokenLockWallet doit avoir une source de données pour zéro adresse',25)
Écrire un test unitaire
Voyons à quoi ressemblerait un test unitaire simple en utilisant les exemples de Gravatar dans le Subgraph de Démo.
En supposant que nous disposions de la fonction de traitement suivante (ainsi que de deux fonctions d’aide pour nous faciliter la vie) :
1export function handleNewGravatar(event: NewGravatar): void {2 let gravatar = new Gravatar(event.params.id.toHex())3 gravatar.owner = event.params.owner4 gravatar.displayName = event.params.displayName5 gravatar.imageUrl = event.params.imageUrl6 gravatar.save()7}89export function handleNewGravatars(events: NewGravatar[]): void {10 events.forEach((event) => {11 handleNewGravatar(event)12 })13}1415export function createNewGravatarEvent(16 id: i32,17 ownerAddress: string,18 displayName: string,19 imageUrl: string,20): NewGravatar {21 let mockEvent = newMockEvent()22 let newGravatarEvent = new NewGravatar(23 mockEvent.address,24 mockEvent.logIndex,25 mockEvent.transactionLogIndex,26 mockEvent.logType,27 mockEvent.block,28 mockEvent.transaction,29 mockEvent.parameters,30 )31 newGravatarEvent.parameters = new Array()32 let idParam = new ethereum.EventParam('id', ethereum.Value.fromI32(id))33 let addressParam = new ethereum.EventParam(34 'ownerAddress',35 ethereum.Value.fromAddress(Address.fromString(ownerAddress)),36 )37 let displayNameParam = new ethereum.EventParam('displayName', ethereum.Value.fromString(displayName))38 let imageUrlParam = new ethereum.EventParam('imageUrl', ethereum.Value.fromString(imageUrl))3940 newGravatarEvent.parameters.push(idParam)41 newGravatarEvent.parameters.push(addressParam)42 newGravatarEvent.parameters.push(displayNameParam)43 newGravatarEvent.parameters.push(imageUrlParam)4445 return newGravatarEvent46}
Nous devons tout d’abord créer un fichier de test dans notre projet. Voici un exemple de ce à quoi cela pourrait ressembler :
1import { clearStore, test, assert } from 'matchstick-as/assembly/index'2import { Gravatar } from '../../generated/schema'3import { NewGravatar } from '../../generated/Gravity/Gravity'4import { createNewGravatarEvent, handleNewGravatars } from '../mappings/gravity'56test('Possibilité d'appeler des mappages avec des événements personnalisés', () => {7 // Créer une entité de test et la sauvegarder dans le store en tant qu'état initial (optionnel)8 let gravatar = new Gravatar('gravatarId0')9 gravatar.save()1011 // Créer des événements fictifs12 let newGravatarEvent = createNewGravatarEvent(12345, '0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7', 'cap', 'pac')13 let anotherGravatarEvent = createNewGravatarEvent(3546, '0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7', 'cap', 'pac')1415 // Appeler les fonctions de mappage en passant par les événements que nous venons de créer16 handleNewGravatars([newGravatarEvent, anotherGravatarEvent])1718 // Affirmer l'état du store19 assert.fieldEquals('Gravatar', 'gravatarId0', 'id', 'gravatarId0')20 assert.fieldEquals('Gravatar', '12345', 'owner', '0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7')21 assert.fieldEquals('Gravatar', '3546', 'displayName', 'cap')2223 // Effacer le store afin de commencer le prochain test sur une ardoise propre24 clearStore()25})2627test('Next test', () => {28 //...29})
Cela fait beaucoup à décortiquer ! Tout d’abord, une chose importante à noter est que nous importons des choses à partir de matchstick-as
, notre bibliothèque d’aide AssemblyScript (distribuée comme un module npm). Vous pouvez trouver le dépôt ici. matchstick-as
nous fournit des méthodes de test utiles et définit également la fonction test()
que nous utiliserons pour construire nos blocs de test. Le reste est assez simple - voici ce qui se passe :
- Mettons en place notre état initial et ajoutons une entité Gravatar personnalisée ;
- Nous définissons deux objets d’événement
NewGravatar
avec leurs données, en utilisant la fonctioncreateNewGravatarEvent()
; - Nous appelons les méthodes de gestion de ces événements -
handleNewGravatars()
et nous passons la liste de nos événements personnalisés ; - Affirmons l’état du magasin. Comment cela fonctionne-t-il ? - Nous passons une combinaison unique de type d’entité et d’identifiant. Ensuite, nous vérifions un champ spécifique de cette entité et affirmons qu’il a la valeur que nous attendons. Nous faisons cela à la fois pour l’entité Gravatar initiale que nous avons ajoutée au magasin, ainsi que pour les deux entités Gravatar qui sont ajoutées lorsque la fonction de gestion est appelée ;
- Enfin, nous nettoyons le store en utilisant
clearStore()
afin que notre prochain test puisse commencer avec un objet de store frais et vide. Nous pouvons définir autant de blocs de test que nous le souhaitons.
Et voilà, nous avons formulé notre premier test ! 👏
Maintenant, pour exécuter nos tests, il vous suffit d’exécuter ce qui suit dans le dossier racine de Subgraph :
graph test Gravity
Et si tout se passe bien, vous devriez être accueilli par ce qui suit :

Scénarios de tests actuels
L’Hydratation du magasin avec un certain état
Les utilisateurs peuvent hydrater le magasin avec un ensemble connu d’entités. Voici un exemple pour initialiser la boutique avec une entité Gravatar :
1laissez gravatar = new Gravatar('entryId')2gravatar.save()
Appel d’une fonction de cartographie avec un événement
Un utilisateur peut créer un événement personnalisé et le transmettre à une fonction de cartographie liée au magasin :
1import { store } from 'matchstick-as/assembly/store'2import { NewGravatar } from '../../generated/Gravity/Gravity'3import { handleNewGravatars, createNewGravatarEvent } from './mapping'45let newGravatarEvent = createNewGravatarEvent(12345, '0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7', 'cap', 'pac')67handleNewGravatar(newGravatarEvent)
Appel de tous les mappages avec des projecteurs d’événements
Les utilisateurs peuvent appeler les mappages avec des dispositifs de test.
1import { NewGravatar } from '../../generated/Gravity/Gravity'2import { store } from 'matchstick-as/assembly/store'3import { handleNewGravatars, createNewGravatarEvent } from './mapping'45let newGravatarEvent = createNewGravatarEvent(12345, '0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7', 'cap', 'pac')67let anotherGravatarEvent = createNewGravatarEvent(3546, '0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7', 'cap', 'pac')89handleNewGravatars([newGravatarEvent, anotherGravatarEvent])
1export function handleNewGravatars(events: NewGravatar[]): void {2 events.forEach(event => {3 handleNewGravatar(event);4 });5}
Appels de contrat moqueurs
Les utilisateurs peuvent simuler des appels de contrat :
1import { addMetadata, assert, createMockedFunction, clearStore, test } from 'matchstick-as/assembly/index'2import { Gravity } from '../../generated/Gravity/Gravity'3import { Address, BigInt, ethereum } from '@graphprotocol/graph-ts'45let contractAddress = Address.fromString('0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7')6let expectedResult = Address.fromString('0x90cBa2Bbb19ecc291A12066Fd8329D65FA1f1947')7let bigIntParam = BigInt.fromString('1234')8createMockedFunction(contractAddress, 'gravatarToOwner', 'gravatarToOwner(uint256):(address)')9 .withArgs([ethereum.Value.fromSignedBigInt(bigIntParam)])10 .returns([ethereum.Value.fromAddress(Address.fromString('0x90cBa2Bbb19ecc291A12066Fd8329D65FA1f1947'))])1112let gravity = Gravity.bind(contractAddress)13let result = gravity.gravatarToOwner(bigIntParam)1415assert.equals(ethereum.Value.fromAddress(expectedResult), ethereum.Value.fromAddress(result))
Comme démontré, afin de se moquer d’un appel de contrat et d’obtenir une valeur de retour, l’utilisateur doit fournir une adresse de contrat, un nom de fonction, une signature de fonction, un tableau d’arguments et bien sûr – la valeur de retour.
Utilisateurs peuvent également simuler des annulations de fonctions :
1laissez contractAddress = Address.fromString('0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7')2createMockedFunction(contractAddress, 'getGravatar', 'getGravatar(address):(string,string)')3 .withArgs([ethereum.Value.fromAddress(contractAddress)])4 .reverts()
Se moquer des fichiers IPFS (à partir de Matchstick 0.4.1)
Les utilisateurs peuvent simuler des fichiers IPFS en utilisant la fonction mockIpfsFile(hash, filePath)
. La fonction accepte deux arguments, le premier étant le hash/chemin du fichier IPFS et le second le chemin d’un fichier local.
NOTE : Lorsque l’on teste ipfs.map/ipfs.mapJSON
, la fonction callback doit être exportée depuis le fichier de test afin que matchtick la détecte, comme la fonction processGravatar()
dans l’exemple de test ci-dessous :
Ficher .test.ts
:
1import { assert, test, mockIpfsFile } from 'matchstick-as/assembly/index'2import { ipfs } from '@graphprotocol/graph-ts'3import { gravatarFromIpfs } from './utils'45// Exporter le callback ipfs.map() pour qu'il soit détecté par matchstick6export { processGravatar } from './utils'78test('ipfs.cat', () => {9 mockIpfsFile('ipfsCatfileHash', 'tests/ipfs/cat.json')1011 assert.entityCount(GRAVATAR_ENTITY_TYPE, 0)1213 gravatarFromIpfs()1415 assert.entityCount(GRAVATAR_ENTITY_TYPE, 1)16 assert.fieldEquals(GRAVATAR_ENTITY_TYPE, '1', 'imageUrl', 'https://i.ytimg.com/vi/MELP46s8Cic/maxresdefault.jpg')1718 clearStore()19})2021test('ipfs.map', () => {22 mockIpfsFile('ipfsMapfileHash', 'tests/ipfs/map.json')2324 assert.entityCount(GRAVATAR_ENTITY_TYPE, 0)2526 ipfs.map('ipfsMapfileHash', 'processGravatar', Value.fromString('Gravatar'), ['json'])2728 assert.entityCount(GRAVATAR_ENTITY_TYPE, 3)29 assert.fieldEquals(GRAVATAR_ENTITY_TYPE, '1', 'displayName', 'Gravatar1')30 assert.fieldEquals(GRAVATAR_ENTITY_TYPE, '2', 'displayName', 'Gravatar2')31 assert.fieldEquals(GRAVATAR_ENTITY_TYPE, '3', 'displayName', 'Gravatar3')32})
Fichier utils.ts
:
1import { Address, ethereum, JSONValue, Value, ipfs, json, Bytes } from "@graphprotocol/graph-ts"2import { Gravatar } from "../../generated/schema"34...56// rappel ipfs.map7export function processGravatar(value: JSONValue, userData: Value): void {8 // Consultez la documentation de JSONValue pour plus de détails sur la façon de traiter les données.9 // avec JSON values10 let obj = value.toObject()11 let id = obj.get('id')1213 if (!id) {14 return15 }1617 // Des entités de rappel peuvent également être créées18 let gravatar = new Gravatar(id.toString())19 gravatar.displayName = userData.toString() + id.toString()20 gravatar.save()21}2223// fonction qui appelle ipfs.cat24export function gravatarFromIpfs(): void {25 let rawData = ipfs.cat("ipfsCatfileHash")2627 if (!rawData) {28 return29 }3031 let jsonData = json.fromBytes(rawData as Bytes).toObject()3233 let id = jsonData.get('id')34 let url = jsonData.get("imageUrl")3536 if (!id || !url) {37 return38 }3940 let gravatar = new Gravatar(id.toString())41 gravatar.imageUrl = url.toString()42 gravatar.save()43}
Affirmation de l’état du magasin
Les utilisateurs sont en mesure d’affirmer l’état final (ou intermédiaire) du magasin via des entités d’affirmation. Pour ce faire, l’utilisateur doit fournir un type d’entité, l’ID spécifique d’une entité, le nom d’un champ sur cette entité et la valeur attendue du champ. Voici un exemple rapide:
1import { assert } from 'matchstick-as/assembly/index'2import { Gravatar } from '../generated/schema'34let gravatar = new Gravatar('gravatarId0')5gravatar.save()67assert.fieldEquals('Gravatar', 'gravatarId0', 'id', 'gravatarId0')
L’exécution de la fonction assert.fieldEquals() permet de vérifier l’égalité du champ donné par rapport à la valeur attendue donnée. Le test échouera et un message d’erreur sera affiché si les valeurs NE SONT PAS égales. Dans le cas contraire, le test passera avec succès.
Interagir avec les métadonnées d’événement
Les utilisateurs peuvent utiliser les métadonnées de transaction par défaut, qui peuvent être reenvoyées comme un ethereum.Event en utilisant la fonction newMockEvent()
. L’exemple suivant montre comment vous pouvez lire/écrire dans ces champs de l’objet Event :
1// Lisez2let logType = newGravatarEvent.logType34// Écrivez5let UPDATED_ADDRESS = '0xB16081F360e3847006dB660bae1c6d1b2e17eC2A'6newGravatarEvent.address = Address.fromString(UPDATED_ADDRESS)
Affirmation de l’égalité des variables
1assert.equals(ethereum.Value.fromString("bonjour"); ethereum.Value.fromString("bonjour"));
Affirmer qu’une entité n’est PAS dans le store
Les utilisateurs peuvent affirmer qu’une entité n’existe pas dans le magasin. La fonction prend un type d’entité et un identifiant. Si l’entité se trouve effectivement dans le magasin, le test échouera avec un message d’erreur pertinent. Voici un exemple rapide de la façon d’utiliser cette fonctionnalité :
1assert.notInStore('Gravatar', '23')
Affichage de tout le magasin ou d’entités individuelles (à des fins de débogage)
Vous pouvez imprimer l’intégralité du magasin sur la console à l’aide de cette fonction d’assistance:
1import { logStore } from 'matchstick-as/assembly/store'23logStore()
Depuis la version 0.6.0, logStore
n’affiche plus les champs dérivés, au lieu de celà les utilisateurs peuvent utiliser la nouvelle fonction logEntity
. Bien sûr, logEntity
peut être utilisée pour afficher n’importe quelle entité, pas seulement celles qui ont des champs dérivés. logEntity
prend le type d’entité, l’identifiant de l’entité et un flag showRelated
pour indiquer si les utilisateurs veulent afficher les entités dérivées associées.
1import { logEntity } from 'matchstick-as/assembly/store'234logEntity("Gravatar", 23, true)
Échec prévu
Les utilisateurs peuvent s’attendre à des échecs de test, en utilisant l’indicateur ShouldFail sur les fonctions test() :
1test(2 'Devrait générer une erreur',3 () => {4 throw new Error()5 },6 true,7)
Si le test est marqué avec ShouldFail = true mais n’échoue PAS, cela apparaîtra comme une erreur dans les journaux et le bloc de test échouera. De plus, s’il est marqué avec ShouldFail = false (l’état par défaut), l’exécuteur de test plantera.
Journal de bord
Avoir des journaux personnalisés dans les tests unitaires équivaut exactement à la journalisation des mappages. La différence est que l’objet journal doit être importé depuis matchstick-as plutôt que graph-ts. Voici un exemple simple avec tous les types de journaux non critiques :
1import { test } from "matchstick-as/assembly/index";2import { log } from "matchstick-as/assembly/log";34test("Success", () => {5 log.success("Succès!". []);6});7test("Error", () => {8 log.error("Erreur :( ", []);9});10test("Debug", () => {11 log.debug("Deboggage...", []);12});13test("Info", () => {14 log.info("Info!", []);15});16test("Warning", () => {17 log.warning("Avertissement!", []);18});
Les utilisateurs peuvent également simuler une panne critique, comme ceci :
1test('Tout faire exploser', () => {2 log.critical('Boom!')3})
La journalisation des erreurs critiques arrêtera l’exécution des tests et fera tout exploser. Après tout, nous voulons nous assurer que votre code ne contient pas de journaux critiques lors du déploiement, et vous devriez le remarquer immédiatement si cela devait se produire.
Tests dérivés
Tester les champs dérivés est une fonctionnalité qui permet aux utilisateurs de définir un champ sur une certaine entité et de faire en sorte qu’une autre entité soit automatiquement mise à jour si elle dérive l’un de ses champs de la première entité.
Avant la version 0.6.0
, il était possible d’obtenir les entités dérivées en y accédant en tant que champs/propriétés d’entité, comme suit :
1let entity = ExampleEntity.load('id')2let derivedEntity = entity.derived_entity
A partir de la version 0.6.0
, ceci est fait en utilisant la fonction loadRelated
de graph-node, les entités dérivées peuvent être accédées de la même manière que dans les gestionnaires.
1test('Derived fields example test', () => {2 let mainAccount = GraphAccount.load('12')!34 assert.assertNull(mainAccount.get('nameSignalTransactions'))5 assert.assertNull(mainAccount.get('operatorOf'))67 let operatedAccount = GraphAccount.load('1')!8 operatedAccount.operators = [mainAccount.id]9 operatedAccount.save()1011 mockNameSignalTransaction('1234', mainAccount.id)12 mockNameSignalTransaction('2', mainAccount.id)1314 mainAccount = GraphAccount.load('12')!1516 assert.assertNull(mainAccount.get('nameSignalTransactions'))17 assert.assertNull(mainAccount.get('operatorOf'))1819 const nameSignalTransactions = mainAccount.nameSignalTransactions.load()20 const operatorsOfMainAccount = mainAccount.operatorOf.load()2122 assert.i32Equals(2, nameSignalTransactions.length)23 assert.i32Equals(1, operatorsOfMainAccount.length)2425 assert.stringEquals('1', operatorsOfMainAccount[0].id)2627 mockNameSignalTransaction('2345', mainAccount.id)2829 let nst = NameSignalTransaction.load('1234')!30 nst.signer = '11'31 nst.save()3233 store.remove('NameSignalTransaction', '2')3435 mainAccount = GraphAccount.load('12')!36 assert.i32Equals(1, mainAccount.nameSignalTransactions.load().length)37})
Test de loadInBlock
Depuis la version 0.6.0
, les utilisateurs peuvent tester loadInBlock
en utilisant le mockInBlockStore
, qui permet de simuler des entités dans le cache du bloc.
1import { afterAll, beforeAll, describe, mockInBlockStore, test } from 'matchstick-as'2import { Gravatar } from '../../generated/schema'34describe('loadInBlock', () => {5 beforeAll(() => {6 mockInBlockStore('Gravatar', 'gravatarId0', gravatar)7 })89 afterAll(() => {10 clearInBlockStore()11 })1213 test('Peut utiliser entity.loadInBlock() pour récupérer l'entité dans le sore du cache du bloc actuel', () => {14 let retrievedGravatar = Gravatar.loadInBlock('gravatarId0')15 assert.stringEquals('gravatarId0', retrievedGravatar!.get('id')!.toString())16 })1718 test("Renvoit null lors de l'appel à entity.loadInBlock() si une entité n'existe pas dans le bloc actuel", () => {19 let retrievedGravatar = Gravatar.loadInBlock('IDoNotExist')20 assert.assertNull(retrievedGravatar)21 })22})
Tester les sources de données dynamiques
Le test des sources de données dynamiques peut être effectué en simulant la valeur de retour des fonctions context()
, address()
et network()
du namespace dataSource. Ces fonctions renvoient actuellement les valeurs suivantes context()
- renvoit une entité vide (DataSourceContext), address()
- renvoit 0x00000000000000000000000000000000
, network()
- renvoit mainnet
. Les fonctions create(...)
et createWithContext(...)
sont simulées pour ne rien faire, donc elles n’ont pas besoin d’être appelées dans les tests. Les modifications des valeurs de retour peuvent être faites à travers les fonctions du namespacedataSourceMock
dans matchstick-as
(version 0.3.0+).
L’exemple ci-dessous :
Nous avons d’abord le gestionnaire d’événements suivant (qui a été intentionnellement réutilisé pour présenter la moquerie de la source de données) :
1export function handleApproveTokenDestinations(event: ApproveTokenDestinations): void {2 let tokenLockWallet = TokenLockWallet.load(dataSource.address().toHexString())!3 if (dataSource.network() == 'rinkeby') {4 tokenLockWallet.tokenDestinationsApproved = true5 }6 let context = dataSource.context()7 if (context.get('contextVal')!.toI32() > 0) {8 tokenLockWallet.setBigInt('tokensReleased', BigInt.fromI32(context.get('contextVal')!.toI32()))9 }10 tokenLockWallet.save()11}
Et puis nous avons le test utilisant l’une des méthodes de l’espace de noms dataSourceMock pour définir une nouvelle valeur de retour pour toutes les fonctions dataSource :
1import { assert, test, newMockEvent, dataSourceMock } from 'matchstick-as/assembly/index'2import { BigInt, DataSourceContext, Value } from '@graphprotocol/graph-ts'34import { handleApproveTokenDestinations } from '../../src/token-lock-wallet'5import { ApproveTokenDestinations } from '../../generated/templates/GraphTokenLockWallet/GraphTokenLockWallet'6import { TokenLockWallet } from '../../generated/schema'78test('Source de données : simple exemple de simulation ', () => {9 let addressString = '0xA16081F360e3847006dB660bae1c6d1b2e17eC2A'10 let address = Address.fromString(addressString)1112 let wallet = new TokenLockWallet(address.toHexString())13 wallet.save()14 let context = new DataSourceContext()15 context.set('contextVal', Value.fromI32(325))16 dataSourceMock.setReturnValues(addressString, 'rinkeby', context)17 let event = changetype<ApproveTokenDestinations>(newMockEvent())1819 assert.assertTrue(!wallet.tokenDestinationsApproved)2021 handleApproveTokenDestinations(event)2223 wallet = TokenLockWallet.load(address.toHexString())!24 assert.assertTrue(wallet.tokenDestinationsApproved)25 assert.bigIntEquals(wallet.tokensReleased, BigInt.fromI32(325))2627 dataSourceMock.resetValues()28})
Notez que dataSourceMock.resetValues() est appelé à la fin. C’est parce que les valeurs sont mémorisées lorsqu’elles sont modifiées et doivent être réinitialisées si vous voulez revenir aux valeurs par défaut.
Test de la création dynamique de sources de données
Depuis la version 0.6.0
, il est possible de tester si une nouvelle source de données a été créée à partir d’un modèle. Cette fonctionnalité prend en charge à la fois les modèles ethereum/contract et file/ipfs. Il y a quatre fonctions pour cela :
assert.dataSourceCount(templateName, expectedCount)
peut être utilisé pour affirmer le nombre attendu de sources de données à partir du modèle spécifiéassert.dataSourceExists(templateName, address/ipfsHash)
affirme qu’une source de données avec l’identifiant spécifié (qui peut être une adresse de contrat ou un hash de fichier IPFS) a été créée à partir d’un modèle spécifiélogDataSources(templateName)
affiche toutes les sources de données du modèle spécifié sur la console à des fins de débogagereadFile(path)
lit un fichier JSON qui représente un fichier IPFS et renvoie le contenu sous forme d’octets
Test des modèles ethereum/contrat
1test('ethereum/contract dataSource creation example', () => {2 // Affirmer qu'il n'y a pas de dataSources créées à partir du modèle GraphTokenLockWallet3 assert.dataSourceCount('GraphTokenLockWallet', 0)45 // Créer une nouvelle source de données GraphTokenLockWallet avec l'adresse 0xA16081F360e3847006dB660bae1c6d1b2e17eC2A6 GraphTokenLockWallet.create(Address.fromString('0xA16081F360e3847006dB660bae1c6d1b2e17eC2A'))78 // Affirmer que la source de données a été créée9 assert.dataSourceCount('GraphTokenLockWallet', 1)1011 // Ajouter une deuxième source de données avec le contexte12 let context = new DataSourceContext()13 context.set('contextVal', Value.fromI32(325))1415 GraphTokenLockWallet.createWithContext(Address.fromString('0xA16081F360e3847006dB660bae1c6d1b2e17eC2B'), context)1617 // Affirmer qu'il y a maintenant 2 sources de données18 assert.dataSourceCount('GraphTokenLockWallet', 2)1920 // Affirme qu'une source de données avec l'adresse "0xA16081F360e3847006dB660bae1c6d1b2e17eC2B" a été créée.21 // Gardez à l'esprit que le type `Address` est transformé en minuscules lorsqu'il est décodé, vous devez donc passer l'adresse en minuscules lorsque vous affirmez qu'elle existe.22 assert.dataSourceExists('GraphTokenLockWallet', '0xA16081F360e3847006dB660bae1c6d1b2e17eC2B'.toLowerCase())2324 logDataSources('GraphTokenLockWallet')25})
Exemple de sortie logDataSource
1🛠 {2 "0xa16081f360e3847006db660bae1c6d1b2e17ec2a": {3 "kind": "ethereum/contract",4 "name": "GraphTokenLockWallet",5 "address": "0xa16081f360e3847006db660bae1c6d1b2e17ec2a",6 "context": null7 },8 "0xa16081f360e3847006db660bae1c6d1b2e17ec2b": {9 "kind": "ethereum/contract",10 "name": "GraphTokenLockWallet",11 "address": "0xa16081f360e3847006db660bae1c6d1b2e17ec2b",12 "context": {13 "contextVal": {14 "type": "Int",15 "data": 32516 }17 }18 }
Test des modèles file/ipfs
De même que pour les sources de données dynamiques de contrat, les utilisateurs peuvent tester les fichiers sources de données et leurs gestionnaires
Exemple subgraph.yaml
1...2templates:3 - kind: file/ipfs4 name: GraphTokenLockMetadata5 network: mainnet6 mapping:7 kind: ethereum/events8 apiVersion: 0.0.99 language: wasm/assemblyscript10 file: ./src/token-lock-wallet.ts11 handler: handleMetadata12 entities:13 - TokenLockMetadata14 abis:15 - name: GraphTokenLockWallet16 file: ./abis/GraphTokenLockWallet.json
Exemple schema.graphql
1"""2Portefeuilles de verrouillage de jetons qui contiennent des GRT verrouillés3"""4type TokenLockMetadata @entity {5 "L'adresse du portefeuille de blocage des jetons"6 id: ID!7 "Heure de début du calendrier de sortie"8 startTime: BigInt!9 "Heure de fin du calendrier de sortie""10 endTime: BigInt!11 "Nombre de périodes entre l'heure de début et l'heure de fin"12 periods: BigInt!13 "Heure à laquelle commence la sortie"14 releaseStartTime: BigInt!15}
Exemple metadata.json
1{2 "startTime": 1,3 "endTime": 1,4 "periods": 1,5 "releaseStartTime": 16}
Exemple de gestionnaire
1export function handleMetadata(content: Bytes): void {2 // dataSource.stringParams() renvoie le CID du fichier de la source de données3 // stringParam() sera simulé dans le test du gestionnaire4 // pour plus d'informations https://thegraph.com/docs/en/developing/creating-a-subgraph/#create-a-new-handler-to-process-files5 let tokenMetadata = new TokenLockMetadata(dataSource.stringParam())6 const value = json.fromBytes(content).toObject()78 if (value) {9 const startTime = value.get('startTime')10 const endTime = value.get('endTime')11 const periods = value.get('periods')12 const releaseStartTime = value.get('releaseStartTime')1314 if (startTime && endTime && periods && releaseStartTime) {15 tokenMetadata.startTime = startTime.toBigInt()16 tokenMetadata.endTime = endTime.toBigInt()17 tokenMetadata.periods = periods.toBigInt()18 tokenMetadata.releaseStartTime = releaseStartTime.toBigInt()19 }2021 tokenMetadata.save()22 }23}
Exemple de test
1import { assert, test, dataSourceMock, readFile } from 'matchstick-as'2import { Address, BigInt, Bytes, DataSourceContext, ipfs, json, store, Value } from '@graphprotocol/graph-ts'34import { handleMetadata } from '../../src/token-lock-wallet'5import { TokenLockMetadata } from '../../generated/schema'6import { GraphTokenLockMetadata } from '../../generated/templates'78test('exemple de création d'une dataSource file/ipfs', () => {9 // Générer le CID de la source de données à partir du fichier ipfsHash + chemin ipfs10 // Par exemple QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm/example.json11 const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'12 const CID = `${ipfshash}/example.json`1314 // Créer une nouvelle dataSource en utilisant le CID généré15 GraphTokenLockMetadata.create(CID)1617 // Affirmer que la dataSource a été créée18 assert.dataSourceCount('GraphTokenLockMetadata', 1)19 assert.dataSourceExists('GraphTokenLockMetadata', CID)20 logDataSources('GraphTokenLockMetadata')2122 // Nous devons maintenant simuler les métadonnées de la dataSource et plus particulièrement dataSource.stringParam()23 // dataSource.stringParams utilise en fait la valeur de dataSource.address(), nous allons donc simuler l'adresse en utilisant dataSourceMock de matchstick-as24 // Nous allons d'abord réinitialiser les valeurs, puis utiliser dataSourceMock.setAddress() pour définir le CID.25 dataSourceMock.resetValues()26 dataSourceMock.setAddress(CID)272829 // Nous devons maintenant générer les octets à transmettre au gestionnaire de la dataSource.30 // Pour ce cas, nous avons introduit une nouvelle fonction readFile, qui lit un json local et renvoie le contenu sous forme d'octets31 const content = readFile(`path/to/metadata.json`)32 handleMetadata(content)3334 // Maintenant nous allons tester si un TokenLockMetadata a été créé35 const metadata = TokenLockMetadata.load(CID)3637 assert.bigIntEquals(metadata !.endTime, BigInt.fromI32(1))38 assert.bigIntEquals(metadata !.periods, BigInt.fromI32(1))39 assert.bigIntEquals(metadata !.releaseStartTime, BigInt.fromI32(1))40 assert.bigIntEquals(metadata !.startTime, BigInt.fromI32(1))41})
Couverture de test
En utilisant Matchstick, les développeurs de Subgraph peuvent exécuter un script qui calculera la couverture des tests unitaires écrits.
L’outil de couverture des tests prend les binaires de test compilés wasm
et les convertit en fichiers wat
, qui peuvent alors être facilement inspectés pour voir si les gestionnaires définis dans subgraph.yaml
ont été appelés ou non. Comme la couverture du code (et les tests dans leur ensemble) n’en est qu’à ses débuts en AssemblyScript et WebAssembly, Matchstick ne peut pas vérifier la couverture des branches. Au lieu de cela, nous nous appuyons sur l’affirmation que si un gestionnaire donné a été appelé, l’événement/la fonction correspondant(e) a été correctement simulé(e).
Prérequis
Pour utiliser la fonctionnalité de couverture des tests fournie dans Matchstick, il y a quelques éléments à préparer à l’avance :
Exportez vos gestionnaires
Pour que Matchstick puisse vérifier quels handlers sont exécutés, ces handlers doivent être exportés depuis le fichier de test. Ainsi, dans notre exemple, dans notre fichier gravity.test.ts, nous avons importé le gestionnaire suivant :
1importez { handleNewGravatar } from '../../src/gravity'
Pour que cette fonction soit visible (pour qu’elle soit incluse dans le fichier wat
par nom), nous devons également l’exporter, comme ceci :
1exportez { handleNewGravatar }
Usage
Une fois tout configuré, pour exécuter l’outil de couverture de test, exécutez simplement :
1graph test -- -c
Vous pouvez également ajouter une commande coverage
personnalisée à votre fichier package.json
, comme ceci :
1"scripts": {2 /.../3 "coverage": "graph test -- -c"4 },
Cela exécutera l’outil de couverture et vous devriez voir quelque chose comme ceci dans le terminal :
1$ graph test -c2Skipping download/install step because binary already exists at /Users/petko/work/demo-subgraph/node_modules/binary-install-raw/bin/0.4.034___ ___ _ _ _ _ _5| \/ | | | | | | | (_) | |6| . . | __ _| |_ ___| |__ ___| |_ _ ___| | __7| |\/| |/ _` | __/ __| '_ \/ __| __| |/ __| |/ /8| | | | (_| | || (__| | | \__ \ |_| | (__| <9\_| |_/\__,_|\__\___|_| |_|___/\__|_|\___|_|\_\1011Compiling...1213Running in coverage report mode.14 ️15Reading generated test modules... 🔎️1617Generating coverage report 📝1819Handlers for source 'Gravity':20Handler 'handleNewGravatar' is tested.21Handler 'handleUpdatedGravatar' is not tested.22Handler 'handleCreateGravatar' is tested.23Test coverage: 66.7% (2/3 handlers).2425Handlers for source 'GraphTokenLockWallet':26Handler 'handleTokensReleased' is not tested.27Handler 'handleTokensWithdrawn' is not tested.28Handler 'handleTokensRevoked' is not tested.29Handler 'handleManagerUpdated' is not tested.30Handler 'handleApproveTokenDestinations' is not tested.31Handler 'handleRevokeTokenDestinations' is not tested.32Test coverage: 0.0% (0/6 handlers).3334Global test coverage: 22.2% (2/9 handlers).
Durée d’exécution du test dans la sortie du journal
La sortie du journal inclut la durée de l’exécution du test. Voici un exemple :
[Thu, 31 Mar 2022 13:54:54 +0300] Program executed in: 42.270ms.
Erreurs de compilation courantes
Critique : impossible de créer WasmInstance à partir d’un module valide avec un contexte : importation inconnue : wasi_snapshot_preview1::fd_write n’a pas été défini
Ceci signifie que vous avez utilisé console.log
dans votre code, qui n’est pas pris en charge par AssemblyScript. Veuillez considérer l’utilisation de Logging API
ERREUR TS2554 : attendu ? arguments, mais j’ai eu ?.
renvoyer le nouveau ethereum.Block (defaultAddressBytes, defaultAddressBytes, defaultAddressBytes, defaultAddress, defaultAddressBytes, defaultAddressBytes, defaultAddressBytes, defaultBigInt, defaultBigInt, defaultBigInt, defaultBigInt, defaultBigInt, defaultBigInt, defaultBigInt, defaultBigInt) ;
dans ~lib/matchstick-as/assembly/defaults.ts(18,12)
ERREUR TS2554 : attendu ? arguments, mais j’ai eu ?.
renvoyer un nouveau ethereum.Transaction (defaultAddressBytes, defaultBigInt, defaultAddress, defaultAddress, defaultBigInt, defaultBigInt, defaultBigInt, defaultAddressBytes, defaultBigInt) ;
dans ~lib/matchstick-as/assembly/defaults.ts(24,12)
La non-concordance des arguments est causée par la non-concordance de graph-ts
et de matchstick-as
. La meilleure façon de résoudre des problèmes comme celui-ci est de tout mettre à jour vers la dernière version publiée.
Ressources supplémentaires
Pour toute aide supplémentaire, consultez cette démo Subgraph repo utilisant Matchstick.
Réaction
Si vous avez des questions, des commentaires, des demandes de fonctionnalités ou si vous souhaitez simplement nous contacter, le meilleur endroit serait The Graph Discord où nous avons une chaîne dédiée à Matchstick, appelée 🔥| tests unitaires.