/depay-web3mock

JavaScript library to mock web3 responses either by emulating blockchain wallets or RPC calls.

Primary LanguageJavaScriptMIT LicenseMIT

Simplify testing your dApps by mocking blockchain providers and wallets with Web3Mock.

Quickstart

yarn add depay-web3mock

or

npm install --save depay-web3mock

Simple

import { Web3Mock } from 'depay-web3mock'

Web3Mock({ mocks: 'ethereum' })

await window.ethereum.request(method: 'eth_chainId') // '0x1'
await window.ethereum.request(method: 'net_version') // 1
await window.ethereum.request(method: 'eth_getBalance') // '0x0'
await window.ethereum.request(method: 'eth_accounts') // ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045"]
await window.ethereum.request(method: 'eth_requestAccounts') // ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045"]

let provider = new ethers.providers.Web3Provider(window.ethereum);
let signer = provider.getSigner();

await signer.sendTransaction({
  to: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
  value: ethers.utils.parseEther("1")
});
// transaction { hash: '0xbb8d9e2262cd2d93d9bf7854d35f8e016dd985e7b3eb715d0d7faf7290a0ff4d', ... }

Complex

import { Web3Mock } from 'depay-web3mock'

Web3Mock({
  mocks: {
    ethereum: {
      calls: {
        "0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb": {
          abi: TokenAbi,
          name: "DePay",
          symbol: "DEPAY",
          balanceOf: {
            "0x5Af489c8786A018EC4814194dC8048be1007e390": "1000000000000000000"
          }
        },
        "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
          abi: UniswapV2RouterAbi,
          getAmountsIn: {
            [
              ["1000000000000000000", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xa0bed124a09ac2bd941b10349d8d224fe3c955eb"]]
            ]: ["773002376389189", "1000000000000000000"]
          }
        }
      },
      transactions: {
        "0x5Af489c8786A018EC4814194dC8048be1007e390": {
          value: "1000000000000000000"
        },
        "0xae60aC8e69414C2Dc362D0e6a03af643d1D85b92": {
          abi: DePayRouterV1Abi,
          method: 'route',
          params: {
            path: ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xa0bed124a09ac2bd941b10349d8d224fe3c955eb"],
            amounts: ["773002376389189", "1000000000000000000", "3623748721"]
          }
        }
      }
    }
  }
})

await contract.name() // DePay
await contract.symbol() // DEPAY
await contract.balanceOf("0x5Af489c8786A018EC4814194dC8048be1007e390") // "1000000000000000000"

await uniswapV2RouterInstance.getAmountsIn("1000000000000000000", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xa0bed124a09ac2bd941b10349d8d224fe3c955eb"])
// ["773002376389189", "1000000000000000000"]

await DePayRouterV1Contract().connect(signer).route(
  path,
  [amountIn, amountOut, deadline],
  [receiver],
  plugins,
  [],
  { value: value }
)
// transaction { hash: '0xbb8d9e2262cd2d93d9bf7854d35f8e016dd985e7b3eb715d0d7faf7290a0ff4d', ... }

Functionalities

Basic Implicit Mocks

Mocks basic blockchain functionalities without explicit mocking:

Web3Mock({
  mocks: ['ethereum']
})

await window.ethereum.request(method: 'eth_chainId') // '0x1'
await window.ethereum.request(method: 'net_version') // 1
await window.ethereum.request(method: 'eth_getBalance') // '0x0'
await window.ethereum.request(method: 'eth_accounts') // ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045"]
await window.ethereum.request(method: 'eth_requestAccounts') // ["0xd8da6bf26964af9d7eed9e03e53415d37aa96045"]
await window.ethereum.request(method: 'eth_estimateGas') // '0x2c4a0'
await window.ethereum.request(method: 'eth_blockNumber') // '0x5daf3b'

Explicit Mocks

Contract Calls

Mock Simple Contract Calls
Web3Mock({
  mocks: {
    ethereum: {
      calls: {
        "0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb": {
          abi: abi,
          name: "DePay",
          symbol: "DEPAY"
        }
      }
    }
  }
})
Mock Simple Contract Calls with Parameters
Web3Mock({
  mocks: {
    ethereum: {
      calls: {
        "0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb": {
          abi: abi,
          balanceOf: {
            "0x5Af489c8786A018EC4814194dC8048be1007e390": "1000000000000000000"              
          }
        }
      }
    }
  }
})
Mock Complex Contract Calls with Complex Parameters
Web3Mock({
  mocks: {
    ethereum: {
      calls: {
        "0x7a250d5630b4cf539739df2c5dacb4c659f2488d": {
          abi: UniswapV2RouterAbi,
          getAmountsIn: {
            [
              ["1000000000000000000", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xa0bed124a09ac2bd941b10349d8d224fe3c955eb"]]
            ]: ["773002376389189", "1000000000000000000"]
          }
        }
      }
    }
  }
})

Transactions

Web3Mock mocks all transactions per default (without adding explicit mocking).

WebMock({ mocks: 'ethereum' })

Web3Mock mocks eth_sendTransaction, eth_getTransactionByHash, eth_getTransactionReceipt request to cover the full lifecycle of blockchain transactions.

Once you set the transactions key in the mock configuration, all transactions are required to be mocked:

WebMock({
  mocks: {
    ethereum: {
      transactions: {}
    }
  }
})

signer.sendTransaction({
  to: "0x5Af489c8786A018EC4814194dC8048be1007e390",
  value: ethers.utils.parseEther("1")
})
// raises: Web3Mock Ethereum transactions: Please mock the transaction: { \"0xd8da6bf26964af9d7eed9e03e53415d37aa96045\": { \"to\": \"0x5af489c8786a018ec4814194dc8048be1007e390\" , \"value\": \"1000000000000000000\"}
Mock Simple Transactions
WebMock({
  mocks: {
    ethereum: {
      transactions: {
        "0x5Af489c8786A018EC4814194dC8048be1007e390": { // to address
          value: "1000000000000000000"
        }
      }
    }
  }
})

signer.sendTransaction({
  to: "0x5Af489c8786A018EC4814194dC8048be1007e390",
  value: ethers.utils.parseEther("1")
})
Mock Complex Contract Transactions
WebMock({
  mocks: {
    ethereum: {
      transactions: {
        '0xae60aC8e69414C2Dc362D0e6a03af643d1D85b92': {
          from: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
          abi: DePayRouterV1Abi,
          method: 'route',
          params: {
            path: ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xa0bed124a09ac2bd941b10349d8d224fe3c955eb"],
            amounts: ["773002376389189", "1000000000000000000", "3623748721"]
          }
        }
      }
    }
  }
})

let provider = new ethers.providers.Web3Provider(window.ethereum);

let contract = new ethers.Contract(
  "0xae60aC8e69414C2Dc362D0e6a03af643d1D85b92",
  [{"inputs":[{"internalType":"address","name":"_configuration","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"configuration","outputs":[{"internalType":"contract DePayRouterV1Configuration","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pluginAddress","type":"address"}],"name":"isApproved","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address[]","name":"addresses","type":"address[]"},{"internalType":"address[]","name":"plugins","type":"address[]"},{"internalType":"string[]","name":"data","type":"string[]"}],"name":"route","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}],
  provider
);

let signer = provider.getSigner();

let transaction = await contract.connect(signer).route(
  ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xa0bed124a09ac2bd941b10349d8d224fe3c955eb"],
  ["773002376389189", "1000000000000000000", "3623748721"],
  [],
  [],
  [],
  { value: 0 }
)

Mock Events

Once Web3Mock has been initialized (e.g. with Web3Mock({ mocks: 'ethereum' })) it starts registering event callbacks from your implementation that you can trigger afterwards in your tests:

Web3Mock({ mocks: 'ethereum' })

// some other prep and test code

Web3Mock.trigger('accountsChanged', ['0xb0252f13850a4823706607524de0b146820F2240', '0xEcA533Ef096f191A35DE76aa4580FA3A722724bE'])

Mock for specific providers

If you want to mock Web3 calls and transactions for other providers but the usual, implicit ones (like window.ethereum), you can pass them explicitly to Web3Mock:

let provider = new ethers.providers.JsonRpcProvider('https://example.com');
    
Web3Mock({
  provider,
  mocks: {
    ethereum: {
      calls: {
        [contractAddress]: {
          abi: abi,
          getAmountsIn: {
            [
              ["1000000000000000000", ["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0xa0bed124a09ac2bd941b10349d8d224fe3c955eb"]]
            ]: ["773002376389189", "1000000000000000000"]
          }
        }
      }
    }
  },
})

window

Pass a window object in case it is not supposed to be the implicit window.

Web3Mock({
  window: anotherObject,
  mocks: ['ethereum']
})

Development

Get started

yarn install
yarn test

Release

npm publish

Testing

Jest

In your jest test environment global is window so Web3Mock takes jest's global automatically when using Web3Mock in a jest test environment.