Skip to content

Commit

Permalink
test scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasrosario authored and Will Cory committed Nov 10, 2023
1 parent b12fade commit 6d0be56
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 0 deletions.
Empty file added src/_test/chains.ts
Empty file.
26 changes: 26 additions & 0 deletions src/_test/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { createConfig, http } from '@wagmi/core'
import { mock } from '@wagmi/core/internal'
import { base as viem_base } from 'viem/chains'
import { accounts } from './constants.js'
import { getRpcUrls } from './utils.js'

const base = {
...getRpcUrls({ port: 8547 }),
...viem_base,
fork: {
blockNumber: process.env.VITE_OPTIMISM_FORK_BLOCK_NUMBER
? BigInt(Number(process.env.VITE_OPTIMISM_FORK_BLOCK_NUMBER))
: 5940037n,
url: process.env.VITE_OPTIMISM_FORK_URL ?? 'https://mainnet.base.org',
},
}

export const config = createConfig({
chains: [base],
connectors: [mock({ accounts })],
pollingInterval: 100,
storage: null,
transports: {
[base.id]: http(),
},
})
75 changes: 75 additions & 0 deletions src/_test/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* The id of the current test worker.
*
* This is used by the anvil proxy to route requests to the correct anvil instance.
*/
export const pool = Number(process.env.VITEST_POOL_ID ?? 1)

// Test accounts
export const accounts = ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'] as const

const messages = new Map()
function warn(message: string) {
if (!messages.has(message)) {
messages.set(message, true)
console.warn(message)
}
}

export let forkBlockNumber: bigint
if (process.env.VITE_ANVIL_BLOCK_NUMBER) {
forkBlockNumber = BigInt(Number(process.env.VITE_ANVIL_BLOCK_NUMBER))
} else {
forkBlockNumber = 18136086n
warn(
`\`VITE_ANVIL_BLOCK_NUMBER\` not found. Falling back to \`${forkBlockNumber}\`.`,
)
}

export let forkUrl: string
if (process.env.VITE_ANVIL_FORK_URL) {
forkUrl = process.env.VITE_ANVIL_FORK_URL
} else {
forkUrl = 'https://cloudflare-eth.com'
warn(`\`VITE_ANVIL_FORK_URL\` not found. Falling back to \`${forkUrl}\`.`)
}

export let blockTime: number
if (process.env.VITE_ANVIL_BLOCK_TIME) {
blockTime = Number(process.env.VITE_ANVIL_BLOCK_TIME)
} else {
blockTime = 1
warn(`\`VITE_ANVIL_BLOCK_TIME\` not found. Falling back to \`${blockTime}\`.`)
}

export let rollupForkBlockNumber: bigint
if (process.env.VITE_ANVIL_ROLLUP_BLOCK_NUMBER) {
rollupForkBlockNumber = BigInt(
Number(process.env.VITE_ANVIL_ROLLUP_BLOCK_NUMBER),
)
} else {
rollupForkBlockNumber = 3709321n
warn(
`\`VITE_ANVIL_ROLLUP_BLOCK_NUMBER\` not found. Falling back to \`${rollupForkBlockNumber}\`.`,
)
}

export let rollupForkUrl: string
if (process.env.VITE_ANVIL_ROLLUP_FORK_URL) {
rollupForkUrl = process.env.VITE_ANVIL_ROLLUP_FORK_URL
} else {
rollupForkUrl = 'https://mainnet.base.org'
warn(
`\`VITE_ANVIL_ROLLUP_FORK_URL\` not found. Falling back to \`${rollupForkUrl}\`.`,
)
}

export let rollupBlockTime: number
if (process.env.VITE_ANVIL_ROLLUP_BLOCK_TIME) {
rollupBlockTime = Number(process.env.VITE_ANVIL_ROLLUP_BLOCK_TIME)
} else {
rollupBlockTime = 1
warn(
`\`VITE_ANVIL_ROLLUP_BLOCK_TIME\` not found. Falling back to \`${rollupBlockTime}\`.`,
)
}
51 changes: 51 additions & 0 deletions src/_test/globalSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// from https://github.com/wagmi-dev/viem/blob/main/src/_test/globalSetup.ts
import { startProxy } from '@viem/anvil'

import {
blockTime,
forkBlockNumber,
forkUrl,
rollupBlockTime,
rollupForkBlockNumber,
rollupForkUrl,
} from './constants.js'

export default async function() {
if (process.env.SKIP_GLOBAL_SETUP) {
return
}

// Using this proxy, we can parallelize our test suite by spawning multiple "on demand" anvil
// instances and proxying requests to them. Especially for local development, this is much faster
// than running the tests serially.
//
// In vitest, each thread is assigned a unique, numerical id (`process.env.VITEST_POOL_ID`). We
// append this id to the local rpc url (e.g. `http://127.0.0.1:8545/<ID>`).
//
// Whenever a request hits the proxy server at this url, it spawns (or reuses) an anvil instance
// at a randomly assigned port and proxies the request to it. The anvil instance is added to a
// [id:port] mapping for future request and is kept alive until the test suite finishes.
//
// Since each thread processes one test file after the other, we don't have to worry about
// non-deterministic behavior caused by multiple tests hitting the same anvil instance concurrently
// as long as we avoid `test.concurrent()`.
//
// We still need to remember to reset the anvil instance between test files. This is generally
// handled in `setup.ts` but may require additional resetting (e.g. via `afterAll`), in case of
// any custom per-test adjustments that persist beyond `anvil_reset`.
await startProxy({
port: 8555,
options: {
forkUrl: rollupForkUrl,
forkBlockNumber: rollupForkBlockNumber,
blockTime: rollupBlockTime,
},
})
return await startProxy({
options: {
forkUrl,
forkBlockNumber,
blockTime,
},
})
}
47 changes: 47 additions & 0 deletions src/_test/react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
renderHook as rtl_renderHook,
type RenderHookOptions,
type RenderHookResult,
waitFor as rtl_waitFor,
type waitForOptions,
} from '@testing-library/react'
import { createElement } from 'react'
import { WagmiProvider } from 'wagmi'
export { act, cleanup } from '@testing-library/react'

import { config } from './config.js'

export const queryClient = new QueryClient()

export function createWrapper<TComponent extends React.FunctionComponent<any>>(
Wrapper: TComponent,
props: Parameters<TComponent>[0],
) {
type Props = { children?: React.ReactNode | undefined }
return function CreatedWrapper({ children }: Props) {
return createElement(
Wrapper,
props,
createElement(QueryClientProvider, { client: queryClient }, children),
)
}
}

export function renderHook<Result, Props>(
render: (props: Props) => Result,
options?: RenderHookOptions<Props> | undefined,
): RenderHookResult<Result, Props> {
queryClient.clear()
return rtl_renderHook(render, {
wrapper: createWrapper(WagmiProvider, { config, reconnectOnMount: false }),
...options,
})
}

export function waitFor<T>(
callback: () => Promise<T> | T,
options?: waitForOptions | undefined,
): Promise<T> {
return rtl_waitFor(callback, { timeout: 10_000, ...options })
}
4 changes: 4 additions & 0 deletions src/_test/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { vi } from 'vitest'

// Make dates stable across runs
Date.now = vi.fn(() => new Date(Date.UTC(2023, 10, 20)).valueOf())
24 changes: 24 additions & 0 deletions src/_test/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { pool } from './constants.js'

export function getRpcUrls({ port }: { port: number }) {
return {
port,
rpcUrls: {
// These rpc urls are automatically used in the transports.
default: {
// Note how we append the worker id to the local rpc urls.
http: [`http://127.0.0.1:${port}/${pool}`],
webSocket: [`ws://127.0.0.1:${port}/${pool}`],
},
public: {
// Note how we append the worker id to the local rpc urls.
http: [`http://127.0.0.1:${port}/${pool}`],
webSocket: [`ws://127.0.0.1:${port}/${pool}`],
},
},
} as const
}

export async function wait(time: number) {
return new Promise((res) => setTimeout(res, time))
}
126 changes: 126 additions & 0 deletions src/hooks/L1/useSimulateDepositETH.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { connect, disconnect } from '@wagmi/core'
import { expect, test } from 'vitest'
import { config } from '../../_test/config.js'
import { accounts } from '../../_test/constants.js'
import { renderHook, waitFor } from '../../_test/react.js'
import { useSimulateDepositETH } from './useSimulateDepositETH.js'

const connector = config.connectors[0]!
const portal = '0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA'

test('default', async () => {
await connect(config, { connector })

const { result } = renderHook(() =>
useSimulateDepositETH({
args: {
to: accounts[0],
gasLimit: 100000,
},
portal,
value: BigInt(1),
dataSuffix: '0x1234',
})
)

await waitFor(() => expect(result.current.isSuccess).toBeTruthy())

expect(result.current).toMatchInlineSnapshot(`
{
"data": {
"request": {
"abi": [
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address",
},
{
"internalType": "uint256",
"name": "_value",
"type": "uint256",
},
{
"internalType": "uint64",
"name": "_gasLimit",
"type": "uint64",
},
{
"internalType": "bool",
"name": "_isCreation",
"type": "bool",
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes",
},
],
"name": "depositTransaction",
"outputs": [],
"stateMutability": "payable",
"type": "function",
},
],
"account": {
"address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"type": "json-rpc",
},
"address": "0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA",
"args": [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
1n,
100000,
false,
"0x",
],
"dataSuffix": "0x1234",
"functionName": "depositTransaction",
"value": 1n,
},
"result": undefined,
},
"dataUpdatedAt": 1700438400000,
"error": null,
"errorUpdateCount": 0,
"errorUpdatedAt": 0,
"failureCount": 0,
"failureReason": null,
"fetchStatus": "idle",
"isError": false,
"isFetched": true,
"isFetchedAfterMount": true,
"isFetching": false,
"isInitialLoading": false,
"isLoading": false,
"isLoadingError": false,
"isPaused": false,
"isPending": false,
"isPlaceholderData": false,
"isRefetchError": false,
"isRefetching": false,
"isStale": true,
"isSuccess": true,
"queryKey": [
"simulateContract",
{
"account": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"blockNumber": undefined,
"chainId": 8453,
"dataSuffix": "0x1234",
"gasLimit": 100000,
"gasPrice": undefined,
"to": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"type": undefined,
"value": undefined,
},
],
"refetch": [Function],
"status": "success",
}
`)

await disconnect(config, { connector })
})
37 changes: 37 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export const poolId = Number(process.env.VITEST_POOL_ID ?? 1)
export const localHttpUrl = `http://127.0.0.1:8545/${poolId}`
export const localWsUrl = `ws://127.0.0.1:8545/${poolId}`
export const localRollupHttpUrl = `http://127.0.0.1:8555/${poolId}`
export const localRollupWsUrl = `ws://127.0.0.1:8555/${poolId}`
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
benchmark: {
outputFile: './bench/report.json',
reporters: process.env.CI ? ['json'] : ['verbose'],
},
// if you are using the default rpc you will need these to not get rate limited
// maxConcurrency: 1,
// maxThreads: 1,
// minThreads: 1,
coverage: {
lines: 95,
statements: 95,
functions: 90,
branches: 93.82,
thresholdAutoUpdate: true,
reporter: ['text', 'json-summary', 'json'],
exclude: [
'**/errors/utils.ts',
'**/dist/**',
'**/*.test.ts',
'**/_test/**',
],
},
environment: 'happy-dom',
globalSetup: ['./src/_test/globalSetup.ts'],
setupFiles: ['./src/_test/setup.ts'],
testTimeout: 100_000,
},
})

0 comments on commit 6d0be56

Please sign in to comment.