From 477b3f62d2cae88f945ee15720cb40f79e53524b Mon Sep 17 00:00:00 2001 From: "Rob Moore (MakerX)" Date: Fri, 20 Dec 2024 13:36:03 +0800 Subject: [PATCH] feat: Renamed `algorandFixture`'s `beforeEach` to `newScope` to make it clearer in intent and ensured that accounts that are registered are copied across successive contexts fix: Resolved bug in transactionLogger.waitForIndexer to only wait for the last transaction, not all logged transactions (which caused problems) --- docs/capabilities/testing.md | 59 +++++++++++++-- .../types_testing.AlgoKitLogCaptureFixture.md | 6 +- .../types_testing.AlgorandFixture.md | 74 +++++++++++++++++-- .../types_testing.LogSnapshotConfig.md | 6 +- docs/code/modules/testing.md | 40 +++++----- src/app-deploy.spec.ts | 2 +- src/app.spec.ts | 2 +- src/indexer-lookup.spec.ts | 2 +- src/testing/fixtures/algorand-fixture.ts | 53 +++++++------ src/testing/transaction-logger.ts | 4 +- src/transaction/transaction.spec.ts | 14 ++-- src/types/account-manager.spec.ts | 2 +- src/types/algorand-client.asset.spec.ts | 2 +- src/types/algorand-client.spec.ts | 2 +- src/types/algorand-client.transfer.spec.ts | 4 +- src/types/app-client.spec.ts | 4 +- src/types/app-factory-and-client.spec.ts | 4 +- src/types/testing.ts | 49 +++++++++++- 18 files changed, 247 insertions(+), 82 deletions(-) diff --git a/docs/capabilities/testing.md b/docs/capabilities/testing.md index 38973ecc..3e6bc373 100644 --- a/docs/capabilities/testing.md +++ b/docs/capabilities/testing.md @@ -24,7 +24,9 @@ import { algorandFixture } from '@algorandfoundation/algokit-utils/testing' ### Using with Jest -To integrate with [Jest](https://jestjs.io/) you need to pass the `fixture.beforeEach` method into Jest's `beforeEach` method and then within each test you can access `fixture.context` to access per-test isolated fixture values. +To integrate with [Jest](https://jestjs.io/) you need to pass the `fixture.newScope` method into Jest's `beforeEach` method (for per test isolation) or `beforeAll` method (for test suite isolation) and then within each test you can access `fixture.context` to access the isolated fixture values. + +#### Per-test isolation ```typescript import { describe, test, beforeEach } from '@jest/globals' @@ -32,7 +34,27 @@ import { algorandFixture } from './testing' describe('MY MODULE', () => { const fixture = algorandFixture() - beforeEach(fixture.beforeEach, 10_000) + beforeEach(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls + + test('MY TEST', async () => { + const { algorand, testAccount /* ... */ } = fixture.context + + // Test stuff! + }) +}) +``` + +Occasionally there may be a delay when first running the fixture setup so we add a 10s timeout to avoid intermittent test failures (`10_000`). + +#### Test suite isolation + +```typescript +import { describe, test, beforeAll } from '@jest/globals' +import { algorandFixture } from './testing' + +describe('MY MODULE', () => { + const fixture = algorandFixture() + beforeAll(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls test('MY TEST', async () => { const { algorand, testAccount /* ... */ } = fixture.context @@ -46,7 +68,9 @@ Occasionally there may be a delay when first running the fixture setup so we add ### Using with vitest -To integrate with [vitest](https://vitest.dev/) you need to pass the `fixture.beforeEach` method into vitest's `beforeEach` method and then within each test you can access `fixture.context` to access per-test isolated fixture values. +To integrate with [vitest](https://vitest.dev/) you need to pass the `fixture.beforeEach` method into vitest's `beforeEach` method (for per test isolation) or `beforeAll` method (for test suite isolation) and then within each test you can access `fixture.context` to access the isolated fixture values. + +#### Per-test isolation ```typescript import { describe, test, beforeEach } from 'vitest' @@ -54,7 +78,27 @@ import { algorandFixture } from './testing' describe('MY MODULE', () => { const fixture = algorandFixture() - beforeEach(fixture.beforeEach, 10_000) + beforeEach(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls + + test('MY TEST', async () => { + const { algorand, testAccount /* ... */ } = fixture.context + + // Test stuff! + }) +}) +``` + +Occasionally there may be a delay when first running the fixture setup so we add a 10s timeout to avoid intermittent test failures (`10_000`). + +#### Test suite isolation + +```typescript +import { describe, test, beforeAll } from 'vitest' +import { algorandFixture } from './testing' + +describe('MY MODULE', () => { + const fixture = algorandFixture() + beforeAll(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls test('MY TEST', async () => { const { algorand, testAccount /* ... */ } = fixture.context @@ -64,6 +108,8 @@ describe('MY MODULE', () => { }) ``` +Occasionally there may be a delay when first running the fixture setup so we add a 10s timeout to avoid intermittent test failures (`10_000`). + ### Fixture configuration When calling `algorandFixture()` you can optionally pass in some fixture configuration, with any of these properties (all optional): @@ -72,6 +118,7 @@ When calling `algorandFixture()` you can optionally pass in some fixture configu - `indexer?: Indexer` - An optional indexer client, if not specified then it will create one against environment variables defined network (if present) or default LocalNet - `kmd?: Kmd` - An optional kmd client, if not specified then it will create one against environment variables defined network (if present) or default LocalNet - `testAccountFunding?: AlgoAmount` - The [amount](./amount.md) of funds to allocate to the default testing account, if not specified then it will get `10` ALGO +- `accountGetter?: (algod: Algodv2, kmd?: Kmd) => Promise` - Optional override for how to get an account; this allows you to retrieve test accounts from a known or cached list of accounts. ### Using the fixture context @@ -84,7 +131,7 @@ The `fixture.context` property is of type [`AlgorandTestAutomationContext`](../c - `transactionLogger: TransactionLogger` - Transaction logger that will log transaction IDs for all transactions issued by `algod` - `testAccount: Account` - Funded test account that is ephemerally created for each test - `generateAccount: (params: GetTestAccountParams) => Promise` - Generate and fund an additional ephemerally created account -- `waitForIndexer: () => Promise` - Wait for the indexer to catch up with all transactions logged by transactionLogger +- `waitForIndexer()` - Waits for indexer to catch up with that latest transaction that has been captured by the `transactionLogger` in the Algorand fixture - `waitForIndexerTransaction: (transactionId: string) => Promise` - Wait for the indexer to catch up with the given transaction ID ## Log capture fixture @@ -170,7 +217,7 @@ This means it's easy to create tests that are flaky and have intermittent test f The testing capability provides mechanisms for waiting for indexer to catch up, namely: - `algotesting.runWhenIndexerCaughtUp(run: () => Promise)` - Executes the given action every 200ms up to 20 times until there is no longer an error with a `status` property with `404` and then returns the result of the action; this will work for any call that calls indexer APIs expecting to return a single record -- `algorandFixture.waitForIndexer()` - Waits for indexer to catch up with all transactions that have been captured by the `transactionLogger` in the Algorand fixture +- `algorandFixture.waitForIndexer()` - Waits for indexer to catch up with that latest transaction that has been captured by the `transactionLogger` in the Algorand fixture - `algorandFixture.waitForIndexerTransaction(transactionId)` - Waits for indexer to catch up with the single transaction of the given ID ## Logging transactions diff --git a/docs/code/interfaces/types_testing.AlgoKitLogCaptureFixture.md b/docs/code/interfaces/types_testing.AlgoKitLogCaptureFixture.md index 2807d874..59c1303b 100644 --- a/docs/code/interfaces/types_testing.AlgoKitLogCaptureFixture.md +++ b/docs/code/interfaces/types_testing.AlgoKitLogCaptureFixture.md @@ -33,7 +33,7 @@ Testing framework agnostic handler method to run after each test to reset the lo #### Defined in -[src/types/testing.ts:113](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L113) +[src/types/testing.ts:158](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L158) ___ @@ -53,7 +53,7 @@ Testing framework agnostic handler method to run before each test to prepare the #### Defined in -[src/types/testing.ts:109](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L109) +[src/types/testing.ts:154](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L154) ## Accessors @@ -69,4 +69,4 @@ The test logger instance for the current test #### Defined in -[src/types/testing.ts:105](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L105) +[src/types/testing.ts:150](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L150) diff --git a/docs/code/interfaces/types_testing.AlgorandFixture.md b/docs/code/interfaces/types_testing.AlgorandFixture.md index 6ad5ee90..0369db41 100644 --- a/docs/code/interfaces/types_testing.AlgorandFixture.md +++ b/docs/code/interfaces/types_testing.AlgorandFixture.md @@ -11,6 +11,7 @@ An Algorand automated testing fixture ### Properties - [beforeEach](types_testing.AlgorandFixture.md#beforeeach) +- [newScope](types_testing.AlgorandFixture.md#newscope) ### Accessors @@ -23,7 +24,66 @@ An Algorand automated testing fixture • **beforeEach**: () => `Promise`\<`void`\> -Testing framework agnostic handler method to run before each test to prepare the `context` for that test. +**`Deprecated`** + +Use newScope instead. +Testing framework agnostic handler method to run before each test to prepare the `context` for that test with per test isolation. + +#### Type declaration + +▸ (): `Promise`\<`void`\> + +##### Returns + +`Promise`\<`void`\> + +#### Defined in + +[src/types/testing.ts:92](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L92) + +___ + +### newScope + +• **newScope**: () => `Promise`\<`void`\> + +Creates a new isolated fixture scope (clean transaction logger, AlgorandClient, testAccount, etc.). + +You can call this from any testing framework specific hook method to control when you want a new scope. + +**`Example`** + +```typescript +describe('MY MODULE', () => { + const fixture = algorandFixture() + beforeEach(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls + + test('MY TEST', async () => { + const { algorand, testAccount } = fixture.context + + // Test stuff! + }) +}) +``` + +**`Example`** + +```typescript +describe('MY MODULE', () => { + const fixture = algorandFixture() + beforeAll(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls + + test('test1', async () => { + const { algorand, testAccount } = fixture.context + + // Test stuff! + }) + test('test2', async () => { + const { algorand, testAccount } = fixture.context + // algorand and testAccount are the same as in test1 + }) +}) +``` #### Type declaration @@ -35,7 +95,7 @@ Testing framework agnostic handler method to run before each test to prepare the #### Defined in -[src/types/testing.ts:87](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L87) +[src/types/testing.ts:132](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L132) ## Accessors @@ -45,13 +105,15 @@ Testing framework agnostic handler method to run before each test to prepare the Retrieve an `AlgorandClient` loaded with the current context, including testAccount and any generated accounts loaded as signers. +If you haven't called `newScope` then this will return an `AlgorandClient` instance with no test context loaded yet and no transaction logger loaded. This is useful if you want to do some basic setup in a `beforeAll` method etc.. + #### Returns [`AlgorandClient`](../classes/types_algorand_client.AlgorandClient.md) #### Defined in -[src/types/testing.ts:82](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L82) +[src/types/testing.ts:86](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L86) ___ @@ -62,6 +124,8 @@ ___ Retrieve the current context. Useful with destructuring. +If you haven't called `newScope` then this will throw an error. + #### Returns [`AlgorandTestAutomationContext`](types_testing.AlgorandTestAutomationContext.md) @@ -70,10 +134,10 @@ Useful with destructuring. ```typescript test('My test', () => { - const {algod, indexer, testAccount, ...} = algorand.context + const {algod, indexer, testAccount, ...} = fixture.context }) ``` #### Defined in -[src/types/testing.ts:77](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L77) +[src/types/testing.ts:79](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L79) diff --git a/docs/code/interfaces/types_testing.LogSnapshotConfig.md b/docs/code/interfaces/types_testing.LogSnapshotConfig.md index 5450914d..3b1b3d0c 100644 --- a/docs/code/interfaces/types_testing.LogSnapshotConfig.md +++ b/docs/code/interfaces/types_testing.LogSnapshotConfig.md @@ -26,7 +26,7 @@ Any accounts/addresses to replace the address for predictably #### Defined in -[src/types/testing.ts:98](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L98) +[src/types/testing.ts:143](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L143) ___ @@ -38,7 +38,7 @@ Any app IDs to replace predictably #### Defined in -[src/types/testing.ts:100](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L100) +[src/types/testing.ts:145](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L145) ___ @@ -50,4 +50,4 @@ Any transaction IDs or transactions to replace the ID for predictably #### Defined in -[src/types/testing.ts:96](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L96) +[src/types/testing.ts:141](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/types/testing.ts#L141) diff --git a/docs/code/modules/testing.md b/docs/code/modules/testing.md index 38226b10..04738d4e 100644 --- a/docs/code/modules/testing.md +++ b/docs/code/modules/testing.md @@ -75,12 +75,12 @@ The fixture **`Example`** ```typescript -const algorand = algorandFixture() +const fixture = algorandFixture() -beforeEach(algorand.beforeEach, 10_000) +beforeEach(fixture.newScope, 10_000) test('My test', async () => { - const {algod, indexer, testAccount, ...} = algorand.context + const {algod, indexer, testAccount, ...} = fixture.context // test things... }) ``` @@ -88,22 +88,35 @@ test('My test', async () => { **`Example`** ```typescript -const algorand = algorandFixture({ +const fixture = algorandFixture() + +beforeAll(fixture.newScope, 10_000) + +test('My test', async () => { + const {algod, indexer, testAccount, ...} = fixture.context + // test things... +}) +``` + +**`Example`** + +```typescript +const fixture = algorandFixture({ algod: new Algodv2('localhost', 12345, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), // ... }) -beforeEach(algorand.beforeEach, 10_000) +beforeEach(fixture.newScope, 10_000) test('My test', async () => { - const {algod, indexer, testAccount, ...} = algorand.context + const {algod, indexer, testAccount, ...} = fixture.context // test things... }) ``` #### Defined in -[src/testing/fixtures/algorand-fixture.ts:48](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/testing/fixtures/algorand-fixture.ts#L48) +[src/testing/fixtures/algorand-fixture.ts:60](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/testing/fixtures/algorand-fixture.ts#L60) ▸ **algorandFixture**(`fixtureConfig`, `config`): [`AlgorandFixture`](../interfaces/types_testing.AlgorandFixture.md) @@ -130,19 +143,6 @@ By default it tests against an environment variable specified client a default LocalNet instance, but you can pass in an algod, indexer and/or kmd if you want to test against an explicitly defined network. -**`Example`** - -```typescript -const algorand = algorandFixture(undefined, getConfigFromEnvOrDefaults()) - -beforeEach(algorand.beforeEach, 10_000) - -test('My test', async () => { - const {algod, indexer, testAccount, ...} = algorand.context - // test things... -}) -``` - #### Defined in [src/testing/fixtures/algorand-fixture.ts:75](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/src/testing/fixtures/algorand-fixture.ts#L75) diff --git a/src/app-deploy.spec.ts b/src/app-deploy.spec.ts index 4e72a171..ce587111 100644 --- a/src/app-deploy.spec.ts +++ b/src/app-deploy.spec.ts @@ -11,7 +11,7 @@ import { LogicError } from './types/logic-error' describe('deploy-app', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + beforeEach(localnet.newScope, 10_000) const logging = algoKitLogCaptureFixture() beforeEach(logging.beforeEach) diff --git a/src/app.spec.ts b/src/app.spec.ts index 6eff3be8..b56f4992 100644 --- a/src/app.spec.ts +++ b/src/app.spec.ts @@ -5,7 +5,7 @@ import { algoKitLogCaptureFixture, algorandFixture } from './testing' describe('app', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + beforeEach(localnet.newScope, 10_000) const logging = algoKitLogCaptureFixture() beforeEach(logging.beforeEach) diff --git a/src/indexer-lookup.spec.ts b/src/indexer-lookup.spec.ts index 03c65de9..19479e24 100644 --- a/src/indexer-lookup.spec.ts +++ b/src/indexer-lookup.spec.ts @@ -7,7 +7,7 @@ import { AlgoAmount } from './types/amount' describe('indexer-lookup', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + beforeEach(localnet.newScope, 10_000) const sendTestTransaction = async (amount?: AlgoAmount, from?: Address) => { return await localnet.context.algorand.send.payment({ diff --git a/src/testing/fixtures/algorand-fixture.ts b/src/testing/fixtures/algorand-fixture.ts index b57bacba..92d30e85 100644 --- a/src/testing/fixtures/algorand-fixture.ts +++ b/src/testing/fixtures/algorand-fixture.ts @@ -15,29 +15,41 @@ import { TransactionLogger } from '../transaction-logger' * and/or kmd (or their respective config) if you want to test against * an explicitly defined network. * - * @example No config + * @example No config (per-test isolation) * ```typescript - * const algorand = algorandFixture() + * const fixture = algorandFixture() * - * beforeEach(algorand.beforeEach, 10_000) + * beforeEach(fixture.newScope, 10_000) * * test('My test', async () => { - * const {algod, indexer, testAccount, ...} = algorand.context + * const {algod, indexer, testAccount, ...} = fixture.context + * // test things... + * }) + * ``` + * + * @example No config (test suite isolation) + * ```typescript + * const fixture = algorandFixture() + * + * beforeAll(fixture.newScope, 10_000) + * + * test('My test', async () => { + * const {algod, indexer, testAccount, ...} = fixture.context * // test things... * }) * ``` * * @example With config * ```typescript - * const algorand = algorandFixture({ + * const fixture = algorandFixture({ * algod: new Algodv2('localhost', 12345, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'), * // ... * }) * - * beforeEach(algorand.beforeEach, 10_000) + * beforeEach(fixture.newScope, 10_000) * * test('My test', async () => { - * const {algod, indexer, testAccount, ...} = algorand.context + * const {algod, indexer, testAccount, ...} = fixture.context * // test things... * }) * ``` @@ -56,18 +68,6 @@ export function algorandFixture(fixtureConfig?: AlgorandFixtureConfig): Algorand * a default LocalNet instance, but you can pass in an algod, indexer * and/or kmd if you want to test against an explicitly defined network. * - * @example - * ```typescript - * const algorand = algorandFixture(undefined, getConfigFromEnvOrDefaults()) - * - * beforeEach(algorand.beforeEach, 10_000) - * - * test('My test', async () => { - * const {algod, indexer, testAccount, ...} = algorand.context - * // test things... - * }) - * ``` - * * @param fixtureConfig The fixture configuration * @param config The fixture configuration * @returns The fixture @@ -84,21 +84,26 @@ export function algorandFixture(fixtureConfig?: AlgorandFixtureConfig, config?: const indexer = fixtureConfig.indexer ?? ClientManager.getIndexerClient(fixtureConfig.indexerConfig!) const kmd = fixtureConfig.kmd ?? ClientManager.getKmdClient(fixtureConfig.kmdConfig!) let context: AlgorandTestAutomationContext - let algorand: AlgorandClient + let algorand = AlgorandClient.fromClients({ algod, indexer, kmd }) - const beforeEach = async () => { + const newScope = async () => { Config.configure({ debug: true }) const transactionLogger = new TransactionLogger() const transactionLoggerAlgod = transactionLogger.capture(algod) + + const previousAccountManager = algorand.account algorand = AlgorandClient.fromClients({ algod: transactionLoggerAlgod, indexer, kmd }) + // Maintain signers across various AlgorandClient instances from the same fixture just in case you have shared accounts being signed from test setup + algorand.account.setSigners(previousAccountManager) + const testAccount = await getTestAccount({ initialFunds: fixtureConfig?.testAccountFunding ?? algos(10), suppressLog: true }, algorand) algorand.setSignerFromAccount(testAccount).setSuggestedParamsCacheTimeout(0) + // If running against LocalNet we are likely in dev mode and we need to set a much higher validity window // otherwise we are more likely to get invalid transactions. if (await algorand.client.isLocalNet()) { algorand.setDefaultValidityWindow(1000) } - algorand.account.setSignerFromAccount(testAccount) context = { algorand, algod: transactionLoggerAlgod, @@ -118,11 +123,13 @@ export function algorandFixture(fixtureConfig?: AlgorandFixtureConfig, config?: return { get context() { + if (!context) throw new Error('Context not initialised; make sure to call fixture.newScope() before accessing context.') return context }, get algorand() { return algorand }, - beforeEach, + beforeEach: newScope, + newScope, } } diff --git a/src/testing/transaction-logger.ts b/src/testing/transaction-logger.ts index 2011b65d..9dade7e7 100644 --- a/src/testing/transaction-logger.ts +++ b/src/testing/transaction-logger.ts @@ -51,7 +51,9 @@ export class TransactionLogger { /** Wait until all logged transactions IDs appear in the given `Indexer`. */ async waitForIndexer(indexer: Indexer) { - await Promise.all(this._sentTransactionIds.map((txnId) => runWhenIndexerCaughtUp(() => indexer.lookupTransactionByID(txnId).do()))) + if (this._sentTransactionIds.length === 0) return + const lastTxId = this._sentTransactionIds[this._sentTransactionIds.length - 1] + await runWhenIndexerCaughtUp(() => indexer.lookupTransactionByID(lastTxId).do()) } } diff --git a/src/transaction/transaction.spec.ts b/src/transaction/transaction.spec.ts index 21097280..915bf4dd 100644 --- a/src/transaction/transaction.spec.ts +++ b/src/transaction/transaction.spec.ts @@ -14,7 +14,7 @@ import { getABIReturnValue, waitForConfirmation } from './transaction' describe('transaction', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + beforeEach(localnet.newScope, 10_000) const getTestTransaction = (amount?: AlgoAmount, sender?: string) => { return { @@ -179,11 +179,11 @@ const tests = (version: 8 | 9) => () => { let appClient: AppClient let externalClient: AppClient - beforeEach(fixture.beforeEach) + beforeEach(fixture.newScope) beforeAll(async () => { Config.configure({ populateAppCallResources: true }) - await fixture.beforeEach() + await fixture.newScope() const { algorand, testAccount } = fixture.context const appFactory = algorand.client.getAppFactory({ @@ -340,12 +340,12 @@ describe('Resource Packer: Mixed', () => { let v9Client: AppClient let v8Client: AppClient - beforeEach(fixture.beforeEach) + beforeEach(fixture.newScope) beforeAll(async () => { Config.configure({ populateAppCallResources: true }) - await fixture.beforeEach() + await fixture.newScope() const testAccount = fixture.context.testAccount @@ -420,10 +420,10 @@ describe('Resource Packer: meta', () => { let externalClient: AppClient - beforeEach(fixture.beforeEach) + beforeEach(fixture.newScope) beforeAll(async () => { - await fixture.beforeEach() + await fixture.newScope() const { algorand, testAccount } = fixture.context Config.configure({ populateAppCallResources: true }) diff --git a/src/types/account-manager.spec.ts b/src/types/account-manager.spec.ts index 6f4069db..b2881e24 100644 --- a/src/types/account-manager.spec.ts +++ b/src/types/account-manager.spec.ts @@ -6,7 +6,7 @@ import { algorandFixture } from '../testing' describe('AccountManager', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10e6) + beforeEach(localnet.newScope, 10e6) test('New account is retrieved and funded', async () => { const { algorand } = localnet.context diff --git a/src/types/algorand-client.asset.spec.ts b/src/types/algorand-client.asset.spec.ts index f273564f..79970a61 100644 --- a/src/types/algorand-client.asset.spec.ts +++ b/src/types/algorand-client.asset.spec.ts @@ -4,7 +4,7 @@ import { generateTestAsset } from '../testing/_asset' describe('Asset capability', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 100_000) + beforeEach(localnet.newScope, 100_000) test('Create an asset succeeds', async () => { const { testAccount, algorand } = localnet.context diff --git a/src/types/algorand-client.spec.ts b/src/types/algorand-client.spec.ts index 8028f6e9..3c177f07 100644 --- a/src/types/algorand-client.spec.ts +++ b/src/types/algorand-client.spec.ts @@ -23,7 +23,7 @@ describe('AlgorandClient', () => { const fixture = algorandFixture() beforeAll(async () => { - await fixture.beforeEach() + await fixture.newScope() alice = fixture.context.testAccount bob = await fixture.context.generateAccount({ initialFunds: AlgoAmount.MicroAlgo(100_000) }) diff --git a/src/types/algorand-client.transfer.spec.ts b/src/types/algorand-client.transfer.spec.ts index 1a68cf38..7a75c3ff 100644 --- a/src/types/algorand-client.transfer.spec.ts +++ b/src/types/algorand-client.transfer.spec.ts @@ -13,7 +13,7 @@ describe('Transfer capability', () => { beforeEach(async () => { vitest.resetModules() process.env = { ...env } - await localnet.beforeEach() + await localnet.newScope() }, 10_000) afterEach(() => { @@ -327,7 +327,7 @@ describe('rekey', () => { beforeEach(async () => { vitest.resetModules() process.env = { ...env } - await localnet.beforeEach() + await localnet.newScope() }, 10_000) afterEach(() => { diff --git a/src/types/app-client.spec.ts b/src/types/app-client.spec.ts index 184c808b..50e08b61 100644 --- a/src/types/app-client.spec.ts +++ b/src/types/app-client.spec.ts @@ -22,7 +22,7 @@ import { AppSpec } from './app-spec' describe('application-client', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + beforeEach(localnet.newScope, 10_000) let appSpec: AppSpec beforeAll(async () => { @@ -870,7 +870,7 @@ describe('application-client', () => { describe('app-client', () => { const localnet = algorandFixture() - beforeEach(localnet.beforeEach, 10_000) + beforeEach(localnet.newScope, 10_000) let appSpec: AppSpec beforeAll(async () => { diff --git a/src/types/app-factory-and-client.spec.ts b/src/types/app-factory-and-client.spec.ts index fdbed46f..72ec06f4 100644 --- a/src/types/app-factory-and-client.spec.ts +++ b/src/types/app-factory-and-client.spec.ts @@ -14,7 +14,7 @@ import { AppSpec } from './app-spec' describe('ARC32: app-factory-and-app-client', () => { const localnet = algorandFixture() beforeEach(async () => { - await localnet.beforeEach() + await localnet.newScope() factory = await localnet.algorand.client.getAppFactory({ appSpec, defaultSender: localnet.context.testAccount }) }, 10_000) @@ -688,7 +688,7 @@ describe('ARC56: app-factory-and-app-client', () => { const localnet = algorandFixture() beforeEach(async () => { - await localnet.beforeEach() + await localnet.newScope() factory = localnet.algorand.client.getAppFactory({ // @ts-expect-error TODO: Fix this diff --git a/src/types/testing.ts b/src/types/testing.ts index 7631cf09..5b7b33a8 100644 --- a/src/types/testing.ts +++ b/src/types/testing.ts @@ -67,10 +67,12 @@ export interface AlgorandFixture { /** * Retrieve the current context. * Useful with destructuring. + * + * If you haven't called `newScope` then this will throw an error. * @example * ```typescript * test('My test', () => { - * const {algod, indexer, testAccount, ...} = algorand.context + * const {algod, indexer, testAccount, ...} = fixture.context * }) * ``` */ @@ -78,13 +80,56 @@ export interface AlgorandFixture { /** * Retrieve an `AlgorandClient` loaded with the current context, including testAccount and any generated accounts loaded as signers. + * + * If you haven't called `newScope` then this will return an `AlgorandClient` instance with no test context loaded yet and no transaction logger loaded. This is useful if you want to do some basic setup in a `beforeAll` method etc.. */ get algorand(): AlgorandClient /** - * Testing framework agnostic handler method to run before each test to prepare the `context` for that test. + * @deprecated Use newScope instead. + * Testing framework agnostic handler method to run before each test to prepare the `context` for that test with per test isolation. */ beforeEach: () => Promise + + /** + * Creates a new isolated fixture scope (clean transaction logger, AlgorandClient, testAccount, etc.). + * + * You can call this from any testing framework specific hook method to control when you want a new scope. + * + * @example Jest / vitest - per test isolation (beforeEach) + * ```typescript + * describe('MY MODULE', () => { + * const fixture = algorandFixture() + * beforeEach(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls + * + * test('MY TEST', async () => { + * const { algorand, testAccount } = fixture.context + * + * // Test stuff! + * }) + * }) + * ``` + * + * @example Jest / vitest - test suite isolation (beforeAll) + * ```typescript + * describe('MY MODULE', () => { + * const fixture = algorandFixture() + * beforeAll(fixture.newScope, 10_000) // Add a 10s timeout to cater for occasionally slow LocalNet calls + * + * test('test1', async () => { + * const { algorand, testAccount } = fixture.context + * + * // Test stuff! + * }) + * test('test2', async () => { + * const { algorand, testAccount } = fixture.context + * // algorand and testAccount are the same as in test1 + * }) + * }) + * ``` + * + */ + newScope: () => Promise } /** Configuration for preparing a captured log snapshot.