DEV Community 👩‍💻👨‍💻

Davide Scalzo
Davide Scalzo

Posted on

A quick intro to end-to-end testing Solana dApps

The challenges of testing decentralised applications

While working on one of the new projects at Jet Protocol it became very apparent that the ecosystem lacked some basic tools needed to run end-to-end tests on Solana dApps. Solana is not alone in providing this challenges, as most decentralised application environments require the user to approve or sign transactions by interacting with a wallet.

If a transaction is not signed with the user private key, the network can not accept the transaction.

This puts the conscientious web3 developer in a bit of a pickle.

On one hand existing automation systems (e.g. Cypress, Puppetteer, Selenium) do not handle interaction browser extensions particularly well, and to make them work you need complicated setups with multiple instances of a test runner (one for the app and one for the extension) and sync them somehow. Messy. Unreliable.

On the other hand, decentralised applications more often than not deal with transactions of a certain value, being tokens, NFT or whatnot, so you want to make sure your application is bullet-proof.

So what do you (we) do?

Do not reinvent the wheel. I repeat. Do not reinvent the wheel.

Pablo Piccasso once said:

Good artists copy, great artists steal 🧑‍🎨

Well it turns out that there is always someone smarter than you in a room. It's hundred of orders of magnitude truer if that room is as big as the web.

That excellent chap named Austin Griffith already solved this problem for the Ethereum blockchain. He developed a burner provider that generated a private key in the browser and allowed the developer to not need to interact with an extension if needed (as is the case for E2E testing, but there are also other uses).

So we've done the same for the Solana blockchain (thanks Austin!).

Introducing @jet-lab/e2e-react-adapter

At this point I could just link you to the @jet-lab/e2e-react-adapter repo and be done as I'm not really used to write blog posts. But I'll try to give you a run down if you bear with me.

This package is designed to work with the solana react provider, but you should be able to get it to work with other implementations (with some tweaks).

This is how a generic Solana react dApp looks like.

import  {  WalletProvider  }  from  '@solana/wallet-adapter-react';
import {
} from '@solana/wallet-adapter-wallets';
import { useMemo } from 'react';
import YourMainApplication from './Main'

export  function  App(): JSX.Element  {
  const wallets = useMemo(() => [
    new PhantomWalletAdapter(),
    new MathWalletAdapter(),
    new SolflareWalletAdapter(),
    new SolongWalletAdapter(),
    new SolletWalletAdapter(),
    new SlopeWalletAdapter(),
  ]}, []);
  return <WalletProvider  wallets={wallets}  autoConnect>
      <YourMainApplication />
Enter fullscreen mode Exit fullscreen mode

To enable e2e testing all you have to do is installing the package and add it to your array of adapters (possibly on your staging / devnet deployments only).

// Other imports
import { E2EWalletAdapter } from 'e2e-react-adapter';

export  function  App(): JSX.Element  {
  const isDev = true // set your devnet logic here
  const wallets = useMemo(() => [
    // other adapters
    new E2EWalletAdapter()
  ]}, [isDev]);
  return <WalletProvider  wallets={wallets}  autoConnect>
      <YourMainApplication />
Enter fullscreen mode Exit fullscreen mode

Now you'll be able to write end-to-end tests for your Solana dApps!

I lied, you're not done yet. But almost!

As the package is intended for devnet / localnet end-to-end testing, you do want to have the transactions actually go through the network and not just interaction with the rpc node. But that needs some SOL to power the transactions.

You have two options here.
One is to have somewhere in your devnet dApp where you can airdrop some SOL to your shiny new burner adapter. It would look something like this:

import { useConnection, useWallet } from '@solana/wallet-adapter-react';

const RequestAirdrop = () => {
  const { connection } = useConnection();
  const { publicKey } = useWallet();

  const requestAirdrop = async () => {
   const signature = await 
   connection.requestAirdrop(publicKey, 1 * LAMPORTS_PER_SOL);
    await connection.confirmTransaction(signature, 'confirmed');

  return <button onClick={requestAirdrop}>Airdrop</button>
Enter fullscreen mode Exit fullscreen mode

And your test can target this button as soon as the adapter is connected.

The other option is to initialise the adapter with a keypair which you've preloaded with devnet SOL. So something like this

new E2EWalletAdapter({
  keypair: <your_keypair_object>
Enter fullscreen mode Exit fullscreen mode

This comes in handy also if you want to run a set of tests in a specific order but your automation program refreshes the browser after each test for instance. Just make sure that you handle any and all private keys safely 🕵️‍♀️

After that you're free to pick whatever E2E framework you prefer, some of the most used ones are Cypress, Puppetteer, TestCafe or Selenium, each with their own traits so pick the one that most suits your needs.

And of course, make sure you don't run tests in production as they would use real SOL, though if you stuck reading till now, you wouldn't do that anyway. Right? Right!

As in most articles around the web thesea are my own personal opinions and not those of Jet.

On that note, about Jet Procol, Jet is an open source, non-custodial liquidity protocol pushing the envelope on decentralized finance and debt capital markets efficiency. Check it out if it piques your interest!

Get In Touch
🌐 Visit us at 🌐
📩 Email us at hello[at]JetProtocol[dot]io 📩
💬 Chat with us on Telegram or Discord 💬
🐦 Find us on Twitter — @JetProtocol 🐦
Subscribe to our newsletter:

Top comments (0)

🌚 Life is too short to browse without dark mode