Skip to content

Commit

Permalink
chore: mock data builder for tests (#2675)
Browse files Browse the repository at this point in the history
* feat: mock data builder for tests

* refactor: pass override object
  • Loading branch information
iamacook authored Oct 23, 2023
1 parent edd6923 commit b1b5871
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 46 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"semver": "^7.5.2"
},
"devDependencies": {
"@faker-js/faker": "^8.1.0",
"@next/bundle-analyzer": "^13.1.1",
"@openzeppelin/contracts": "^4.9.2",
"@safe-global/safe-core-sdk-types": "^1.9.1",
Expand Down
33 changes: 15 additions & 18 deletions src/components/common/AddressInput/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { useForm, FormProvider } from 'react-hook-form'
import AddressInput, { type AddressInputProps } from '.'
import { useCurrentChain } from '@/hooks/useChains'
import useNameResolver from '@/components/common/AddressInput/useNameResolver'
import { chainBuilder } from '@/tests/builders/chains'
import { FEATURES } from '@safe-global/safe-gateway-typescript-sdk'

const mockChain = chainBuilder()
.with({ features: [FEATURES.DOMAIN_LOOKUP] })
.build()

// mock useCurrentChain
jest.mock('@/hooks/useChains', () => ({
useCurrentChain: jest.fn(() => ({
shortName: 'gor',
chainId: '5',
chainName: 'Goerli',
features: ['DOMAIN_LOOKUP'],
})),
useCurrentChain: jest.fn(() => mockChain),
}))

// mock useNameResolver
Expand Down Expand Up @@ -92,7 +93,7 @@ describe('AddressInput tests', () => {
)

act(() => {
fireEvent.change(input, { target: { value: 'gor:0x123' } })
fireEvent.change(input, { target: { value: `${mockChain.shortName}:0x123` } })
jest.advanceTimersByTime(1000)
})

Expand All @@ -103,14 +104,14 @@ describe('AddressInput tests', () => {
const { input, utils } = setup('', (val) => `${val} is wrong`)

act(() => {
fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_A}` } })
fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_A}` } })
jest.advanceTimersByTime(1000)
})

await waitFor(() => expect(utils.getByLabelText(`${TEST_ADDRESS_A} is wrong`, { exact: false })).toBeDefined())

act(() => {
fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_B}` } })
fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_B}` } })
jest.advanceTimersByTime(1000)
})

Expand All @@ -126,7 +127,7 @@ describe('AddressInput tests', () => {
})

act(() => {
fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_A}` } })
fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_A}` } })
jest.advanceTimersByTime(1000)
})

Expand Down Expand Up @@ -195,17 +196,13 @@ describe('AddressInput tests', () => {
})

it('should not show the adornment prefix when the value contains correct prefix', async () => {
;(useCurrentChain as jest.Mock).mockImplementation(() => ({
shortName: 'gor',
chainId: '5',
chainName: 'Goerli',
features: [],
}))
const mockChain = chainBuilder().with({ features: [] }).build()
;(useCurrentChain as jest.Mock).mockImplementation(() => mockChain)

const { input } = setup(`gor:${TEST_ADDRESS_A}`)
const { input } = setup(`${mockChain.shortName}:${TEST_ADDRESS_A}`)

act(() => {
fireEvent.change(input, { target: { value: `gor:${TEST_ADDRESS_B}` } })
fireEvent.change(input, { target: { value: `${mockChain.shortName}:${TEST_ADDRESS_B}` } })
})

await waitFor(() => expect(input.previousElementSibling?.textContent).toBe(''))
Expand Down
5 changes: 2 additions & 3 deletions src/components/common/CheckWallet/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import CheckWallet from '.'
import useIsOnlySpendingLimitBeneficiary from '@/hooks/useIsOnlySpendingLimitBeneficiary'
import useIsSafeOwner from '@/hooks/useIsSafeOwner'
import useWallet from '@/hooks/wallets/useWallet'
import { chainBuilder } from '@/tests/builders/chains'

// mock useWallet
jest.mock('@/hooks/wallets/useWallet', () => ({
Expand All @@ -27,9 +28,7 @@ jest.mock('@/hooks/useIsOnlySpendingLimitBeneficiary', () => ({
// mock useCurrentChain
jest.mock('@/hooks/useChains', () => ({
__esModule: true,
useCurrentChain: jest.fn(() => ({
chainName: 'Optimism',
})),
useCurrentChain: jest.fn(() => chainBuilder().build()),
}))

const renderButton = () => render(<CheckWallet>{(isOk) => <button disabled={!isOk}>Continue</button>}</CheckWallet>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import * as txMonitor from '@/services/tx/txMonitor'
import * as usePendingSafe from '@/components/new-safe/create/steps/StatusStep/usePendingSafe'
import { JsonRpcProvider, Web3Provider } from '@ethersproject/providers'
import type { ConnectedWallet } from '@/hooks/wallets/useOnboard'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { chainBuilder } from '@/tests/builders/chains'
import { BigNumber } from '@ethersproject/bignumber'
import { waitFor } from '@testing-library/react'
import type Safe from '@safe-global/safe-core-sdk'
import { hexZeroPad } from 'ethers/lib/utils'
import type CompatibilityFallbackHandlerEthersContract from '@safe-global/safe-ethers-lib/dist/src/contracts/CompatibilityFallbackHandler/CompatibilityFallbackHandlerEthersContract'
import { FEATURES } from '@/utils/chains'
import { FEATURES } from '@safe-global/safe-gateway-typescript-sdk'
import * as gasPrice from '@/hooks/useGasPrice'

const mockSafeInfo = {
Expand Down Expand Up @@ -44,10 +44,7 @@ describe('useSafeCreation', () => {
beforeEach(() => {
jest.resetAllMocks()

const mockChain = {
chainId: '4',
features: [],
} as unknown as ChainInfo
const mockChain = chainBuilder().with({ features: [] }).build()

jest.spyOn(web3, 'useWeb3').mockImplementation(() => mockProvider)
jest.spyOn(web3, 'getWeb3ReadOnly').mockImplementation(() => mockProvider)
Expand Down Expand Up @@ -89,12 +86,10 @@ describe('useSafeCreation', () => {
false,
])

jest.spyOn(chain, 'useCurrentChain').mockImplementation(
() =>
({
chainId: '4',
features: [FEATURES.EIP1559],
} as unknown as ChainInfo),
jest.spyOn(chain, 'useCurrentChain').mockImplementation(() =>
chainBuilder()
.with({ features: [FEATURES.EIP1559] })
.build(),
)
jest.spyOn(usePendingSafe, 'usePendingSafe').mockReturnValue([mockPendingSafe, mockSetPendingSafe])

Expand Down
5 changes: 3 additions & 2 deletions src/components/tx-flow/flows/SignMessage/SignMessage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { hexlify, hexZeroPad, toUtf8Bytes } from 'ethers/lib/utils'
import type { ChainInfo, SafeInfo, SafeMessage, SafeMessageListPage } from '@safe-global/safe-gateway-typescript-sdk'
import type { SafeInfo, SafeMessage, SafeMessageListPage } from '@safe-global/safe-gateway-typescript-sdk'
import { SafeMessageListItemType } from '@safe-global/safe-gateway-typescript-sdk'

import SignMessage from './SignMessage'
Expand All @@ -17,6 +17,7 @@ import type { EIP1193Provider, WalletState, AppState, OnboardAPI } from '@web3-o
import { generateSafeMessageHash } from '@/utils/safe-messages'
import { getSafeMessage } from '@safe-global/safe-gateway-typescript-sdk'
import useSafeMessages from '@/hooks/messages/useSafeMessages'
import { chainBuilder } from '@/tests/builders/chains'

jest.mock('@safe-global/safe-gateway-typescript-sdk', () => ({
...jest.requireActual('@safe-global/safe-gateway-typescript-sdk'),
Expand Down Expand Up @@ -400,7 +401,7 @@ describe('SignMessage', () => {
jest.spyOn(onboard, 'default').mockReturnValue(mockOnboard)
jest.spyOn(useIsSafeOwnerHook, 'default').mockImplementation(() => true)
jest.spyOn(useIsWrongChainHook, 'default').mockImplementation(() => true)
jest.spyOn(useChainsHook, 'useCurrentChain').mockReturnValue({ chainName: 'Goerli' } as ChainInfo)
jest.spyOn(useChainsHook, 'useCurrentChain').mockReturnValue(chainBuilder().build())

const { getByText } = render(
<SignMessage
Expand Down
16 changes: 9 additions & 7 deletions src/hooks/__tests__/useRemainingRelays.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import { renderHook, waitFor } from '@/tests/test-utils'
import { useLeastRemainingRelays, useRelaysBySafe } from '@/hooks/useRemainingRelays'
import * as useSafeInfo from '@/hooks/useSafeInfo'
import * as useChains from '@/hooks/useChains'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'
import { chainBuilder } from '@/tests/builders/chains'
import { FEATURES } from '@/utils/chains'
import { SAFE_RELAY_SERVICE_URL } from '@/services/tx/relaying'

const SAFE_ADDRESS = '0x0000000000000000000000000000000000000001'

describe('fetch remaining relays hooks', () => {
const mockChain = chainBuilder()
// @ts-expect-error - using local FEATURES enum
.with({ features: [FEATURES.RELAYING] })
.build()
beforeEach(() => {
jest
.spyOn(useChains, 'useCurrentChain')
.mockReturnValue({ chainId: '5', features: FEATURES.RELAYING } as unknown as ChainInfo)
jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(mockChain)
jest.spyOn(useSafeInfo, 'default').mockReturnValue({
safe: {
txHistoryTag: '0',
Expand Down Expand Up @@ -41,15 +43,15 @@ describe('fetch remaining relays hooks', () => {
global.fetch = jest.fn()
const mockFetch = jest.spyOn(global, 'fetch')

const url = `${SAFE_RELAY_SERVICE_URL}/5/${SAFE_ADDRESS}`
const url = `${SAFE_RELAY_SERVICE_URL}/${mockChain.chainId}/${SAFE_ADDRESS}`

renderHook(() => useRelaysBySafe())
expect(mockFetch).toHaveBeenCalledTimes(1)
expect(mockFetch).toHaveBeenCalledWith(url)
})

it('should not do a network request if chain does not support relay', () => {
jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ chainId: '5', features: [] } as unknown as ChainInfo)
jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(chainBuilder().with({ features: [] }).build())

global.fetch = jest.fn()
const mockFetch = jest.spyOn(global, 'fetch')
Expand Down Expand Up @@ -151,7 +153,7 @@ describe('fetch remaining relays hooks', () => {
})

it('should not do a network request if chain does not support relay', () => {
jest.spyOn(useChains, 'useCurrentChain').mockReturnValue({ chainId: '5', features: [] } as unknown as ChainInfo)
jest.spyOn(useChains, 'useCurrentChain').mockReturnValue(chainBuilder().with({ features: [] }).build())
global.fetch = jest
.fn()
.mockResolvedValue({
Expand Down
10 changes: 6 additions & 4 deletions src/hooks/useMnemonicName/useMnemonicName.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { getRandomName, useMnemonicName, useMnemonicSafeName } from '.'
import { renderHook } from '@/tests/test-utils'
import { chainBuilder } from '@/tests/builders/chains'

const mockChain = chainBuilder().build()

// Mock useCurrentChain hook
jest.mock('@/hooks/useChains', () => ({
useCurrentChain: () => ({
chainName: 'Rinkeby',
}),
useCurrentChain: () => mockChain,
}))

describe('useMnemonicName tests', () => {
Expand Down Expand Up @@ -37,6 +38,7 @@ describe('useMnemonicName tests', () => {

it('should return a random safe name', () => {
const { result } = renderHook(() => useMnemonicSafeName())
expect(result.current).toMatch(/^[a-z-]+-rinkeby-safe$/)
const regex = new RegExp(`^[a-z-]+-${mockChain.chainName.toLowerCase()}-safe$`)
expect(result.current).toMatch(regex)
})
})
30 changes: 30 additions & 0 deletions src/tests/Builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export interface IBuilder<T> {
with(override: Partial<T>): IBuilder<T>

build(): T
}

export class Builder<T> implements IBuilder<T> {
private constructor(private target: Partial<T>) {}

/**
* Returns a new {@link Builder} with the property {@link key} set to {@link value}.
*
* @param override - the override value to apply
*/
with(override: Partial<T>): IBuilder<T> {
const target: Partial<T> = { ...this.target, ...override }
return new Builder<T>(target)
}

/**
* Returns an instance of T with the values that were set so far
*/
build(): T {
return this.target as T
}

public static new<T>(): IBuilder<T> {
return new Builder<T>({})
}
}
Loading

0 comments on commit b1b5871

Please sign in to comment.