> For the complete documentation index, see [llms.txt](https://docs.mimic.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.mimic.fi/developers/tests.md).

# Tests

The Mimic Protocol Test Library (`@mimicprotocol/test-ts`) provides tools for simulating function execution to validate expected function behavior under different scenarios.

***

### 1. Getting Started

#### 1.1. Basic test structure

Every test follows this structure:

```tsx
import { runFunction /* types */ } from "@mimicprotocol/test-ts";

describe("Function", () => {
  // 1. Define context, inputs, mocks
  const functionDir = "./build";
  const context = {
    /* required fields */
  };
  const inputs = {
    /* manifest inputs */
  };

  it("produces the expected intents", async () => {
    // 2. Execute the function
    const result = await runFunction(functionDir, context, {
      inputs /* needed mocks */,
    });

    // 3. Check the function outputs
    expect(result.success).to.be.true;
    expect(result.intents).to.have.lengthOf(N);
  });
});
```

#### 1.2. Project setup

Create a basic function project:

```bash
# Initialize a new Mimic project
npx @mimicprotocol/cli init my-automation-function && cd my-automation-function

# Test your function
yarn mimic test
```

***

### 2. Function Runner Reference

This section describes the parameters and outputs of the `runFunction` function.

#### 2.1. Parameters

**2.1.1. Function directory**

The directory where the compiled function `.wasm` is located.

```tsx
const functionDir = "./build";
```

**2.1.2. Context**

The context includes the fields needed during function execution.

```tsx
import { Context } from "@mimicprotocol/test-ts";

const context: Context = {
  user: "0xAddress",
  settlers: [{ address: "0xAddress", chainId: 10 }], // One per chain used in the function
  timestamp: Date.now(), // number (in milliseconds)
};
```

**2.1.3. Inputs**

The values for the inputs defined in the manifest. For example, if the manifest declares:

```yaml
inputs:
  - chainId: uint32
  - token: address
  - amount: string
  - feeAmount: uint256
```

Then, the inputs may be:

```tsx
const inputs = {
  chainId: 10,
  token: "0xAddress",
  amount: "1.5", // 1.5 tokens
  feeAmount: "100000", // 0.1 tokens (6 decimals)
};
```

**2.1.4. Prices mock**

The responses for the price queries made in the function.

For example, if the function does:

```tsx
import { environment } from "@mimicprotocol/lib-ts";

const price = environment.tokenPriceQuery(dai).unwrap();
// or
const amountInUsd = amountInDai.toUsd().unwrap();
// or
const wethAmount = usdcAmount.toTokenAmount(weth).unwrap();
```

Then, the prices mock may be:

```tsx
import { TokenPriceQueryMock } from "@mimicprotocol/test-ts";
import { fp } from "@mimicprotocol/sdk";

const prices: TokenPriceQueryMock[] = [
  // Mock for `tokenPriceQuery` and `toUsd`
  {
    request: { token: "0xDAI", chainId: 10 },
    response: [fp(1).toString()], // 1 DAI = 1 USD
  },
  // Mocks for `toTokenAmount`
  {
    request: { token: "0xUSDC", chainId: 10 },
    response: [fp(0.99).toString()], // 1 USDC = 0.99 USD
  },
  {
    request: { token: "0xWETH", chainId: 10 },
    response: [fp(4200).toString()], // 1 WETH = 4200 USD
  },
];
```

**2.1.5. Relevant tokens mock**

The responses for the relevant tokens queries made in the function.

For example, if the function does:

```tsx
import { ChainId, environment } from "@mimicprotocol/lib-ts";

const userTokens = environment
  .relevantTokensQuery(
    context.user,
    [ChainId.OPTIMISM],
    USD.zero(),
    [USDC, USDT],
    ListType.AllowList,
  )
  .unwrap();
```

Then, the relevant tokens mock may be:

```tsx
import { RelevantTokensQueryMock } from "@mimicprotocol/test-ts";

const relevantTokens: RelevantTokensQueryMock[] = [
  {
    request: {
      owner: "0xAddress",
      chainIds: [10],
      usdMinAmount: "0",
      tokenFilter: 0, // AllowList = 0, DenyList = 1
      tokens: [
        { address: "0xUSDC", chainId: 10 },
        { address: "0xUSDT", chainId: 10 },
      ],
    },
    response: [
      {
        timestamp: context.timestamp,
        balances: [
          { token: { address: "0xUSDC", chainId: 10 }, balance: "10000000" }, // 10 USDC
          { token: { address: "0xUSDT", chainId: 10 }, balance: "10000" }, // 0.01 USDT
        ],
      },
    ],
  },
];
```

**2.1.6. Contract calls mock**

The responses for the contract calls made in the function. Only for read functions, i.e., those that are not intended to generate intents.\
Note: Token `decimals` and `symbol` are sometimes queried behind the scenes and need mocks in those cases.

For example, if the function does:

```tsx
// `TokenAmount#fromStringDecimal` calls `decimals`
const tokenAmount = TokenAmount.fromStringDecimal(USDC, "10.5");

// `TokenAmount#toString` calls `symbol`
log.info(`Transfer amount: ${tokenAmount}`);

// `balanceOf` needs a mock
const tokenContract = new ERC20(USDC, ChainId.OPTIMISM);
const balance = tokenContract.balanceOf(recipient).unwrap();

// `mint` does not need a mock
tokenContract.mint(recipient, amount).build().send();
```

Then, the calls mock may be:

```tsx
import { EvmCallQueryMock } from "@mimicprotocol/test-ts";
import { Interface } from "ethers";

import ERC20Abi from "../abis/ERC20.json";

const ERC20Interface = new Interface(ERC20Abi);

const calls: EvmCallQueryMock[] = [
  {
    request: {
      chainId: 10,
      to: "0xUSDC",
      fnSelector: ERC20Interface.getFunction("decimals").selector,
    },
    response: { value: "6", abiType: "uint8" },
  },
  {
    request: {
      chainId: 10,
      to: "0xUSDC",
      fnSelector: ERC20Interface.getFunction("symbol").selector,
    },
    response: { value: "USDC", abiType: "string" },
  },
  {
    request: {
      chainId: 10,
      to: "0xUSDC",
      fnSelector: ERC20Interface.getFunction("balanceOf").selector,
      params: [{ value: "0xAddress", abiType: "address" }],
    },
    response: { value: "10000000", abiType: "uint256" }, // 10 USDC
  },
];
```

**2.1.7. Subgraph queries mock**

The responses for the subgraph queries made in the function.

For example, if the function does:

```tsx
import { ChainId, environment } from "@mimicprotocol/lib-ts";

const response = environment
  .subgraphQuery(
    ChainId.OPTIMISM,
    "QmSubgraphId",
    "{ tokens { symbol holders } }",
  )
  .unwrap();
```

Then, the subgraph queries mock may be:

```tsx
import { SubgraphQueryMock } from "@mimicprotocol/test-ts";

const subgraphQueries: SubgraphQueryMock[] = [
  {
    request: {
      timestamp: context.timestamp,
      chainId: 10,
      subgraphId: "QmSubgraphId",
      query: "{ tokens { id symbol } }",
    },
    response: {
      blockNumber: 1,
      data: '{ "tokens": [{ "symbol": "WETH", "holders": "1857" }] }',
    },
  },
];
```

#### 2.2. Output

The `runFunction` function returns an object containing the following fields:

* `success` - Boolean. True if the execution ended properly, or false if it had an error.
* `timestamp` - Number. Execution timestamp in milliseconds.
* `fuelUsed` - Number. Amount of fuel used during the execution.
* `intents` - Array of intents produced by the execution.
* `logs` - Array of logs produced by the execution. It may include error logs.

**2.2.1. Intents**

Each intent in `result.intents` contains intent-level metadata (`settler`, `feePayer`, `maxFees`) and an `operations` array. The shape of each operation depends on its type.

**Transfer**

If the function creates a transfer intent:

```typescript
const USDC = ERC20Token.fromString("0xUSDC", ChainId.OPTIMISM);
const recipient = Address.fromString("0xRecipient");
const amount = BigInt.fromStringDecimal("1", USDC.decimals);
const maxFee = TokenAmount.fromStringDecimal(USDC, "0.1");

TransferBuilder.forChain(ChainId.OPTIMISM)
  .addTransferFromTokenAmount(TokenAmount.fromBigInt(USDC, amount), recipient)
  .build()
  .send(maxFee);
```

Then, the test should be:

```typescript
import { Chains, OpType } from "@mimicprotocol/sdk";
import { runFunction, TransferOperation } from "@mimicprotocol/test-ts";

it("produces the expected intent", async () => {
  const result = await runFunction(/* parameters */);
  expect(result.success).to.be.true;

  expect(result.intents).to.have.lengthOf(1);
  const intent = result.intents[0];
  const op = intent.operations[0] as TransferOperation;

  expect(op.opType).to.be.equal(OpType.Transfer);
  expect(intent.settler).to.be.equal(context.settlers[0].address);
  expect(op.user).to.be.equal(context.user);
  expect(op.chainId).to.be.equal(Chains.Optimism);

  expect(op.transfers).to.have.lengthOf(1);
  expect(op.transfers[0].token).to.be.equal("0xUSDC");
  expect(op.transfers[0].amount).to.be.equal("1000000"); // 1 USDC
  expect(op.transfers[0].recipient).to.be.equal("0xRecipient");

  expect(intent.feePayer).to.be.equal(context.user);
  expect(intent.maxFees).to.have.lengthOf(1);
  expect(intent.maxFees[0].token).to.be.equal("0xUSDC");
  expect(intent.maxFees[0].amount).to.be.equal("100000"); // 0.1 USDC
});
```

**Swap**

If the function creates a swap intent:

```typescript
const USDC = ERC20Token.fromString("0xUSDC", ChainId.OPTIMISM);
const amountIn = BigInt.fromStringDecimal("1", USDC.decimals);

const WETH = ERC20Token.fromString("0xWETH", ChainId.OPTIMISM);
const minAmountOut = BigInt.fromStringDecimal("0.001", WETH.decimals);

const recipient = environment.getContext().user;

SwapBuilder.forChains(ChainId.OPTIMISM, ChainId.OPTIMISM)
  .addTokenInFromTokenAmount(TokenAmount.fromBigInt(USDC, amountIn))
  .addTokenOutFromTokenAmount(
    TokenAmount.fromBigInt(WETH, minAmountOut),
    recipient,
  )
  .build()
  .send();
```

Then, the test should be:

```typescript
import { Chains, OpType } from "@mimicprotocol/sdk";
import { runFunction, SwapOperation } from "@mimicprotocol/test-ts";

it("produces the expected intent", async () => {
  const result = await runFunction(/* parameters */);
  expect(result.success).to.be.true;

  expect(result.intents).to.have.lengthOf(1);
  const intent = result.intents[0];
  const op = intent.operations[0] as SwapOperation;

  expect(op.opType).to.be.equal(OpType.Swap);
  expect(intent.settler).to.be.equal(context.settlers[0].address);
  expect(op.user).to.be.equal(context.user);
  expect(op.sourceChain).to.be.equal(Chains.Optimism);
  expect(op.destinationChain).to.be.equal(Chains.Optimism);

  expect(op.tokensIn).to.have.lengthOf(1);
  expect(op.tokensIn[0].token).to.be.equal("0xUSDC");
  expect(op.tokensIn[0].amount).to.be.equal("1000000"); // 1 USDC

  expect(op.tokensOut).to.have.lengthOf(1);
  expect(op.tokensOut[0].token).to.be.equal("0xWETH");
  expect(op.tokensOut[0].minAmount).to.be.equal("1" + "0".repeat(15)); // 0.001 WETH
  expect(op.tokensOut[0].recipient).to.be.equal(context.user);

  expect(intent.feePayer).to.be.equal(context.user);
  expect(intent.maxFees).to.have.lengthOf(0);
});
```

**Call**

If the function creates a call intent:

```typescript
const USDC = ERC20Token.fromString("0xUSDC", ChainId.OPTIMISM);
const amount = BigInt.fromStringDecimal("1", USDC.decimals);
const maxFee = TokenAmount.fromStringDecimal(USDC, "0.1");
const data = ERC20Utils.encodeApprove(spender, amount);

EvmCallBuilder.forChain(ChainId.OPTIMISM)
  .addCall(USDC, data)
  .addUser(smartAccount)
  .build()
  .send(maxFee);
```

Then, the test should be:

```typescript
import { Chains, OpType } from "@mimicprotocol/sdk";
import { EvmCallOperation, runFunction } from "@mimicprotocol/test-ts";
import { Interface } from "ethers";

import ERC20Abi from "../abis/ERC20.json";

const ERC20Interface = new Interface(ERC20Abi);

it("produces the expected intent", async () => {
  const result = await runFunction(/* parameters */);
  expect(result.success).to.be.true;

  expect(result.intents).to.have.lengthOf(1);
  const intent = result.intents[0];
  const op = intent.operations[0] as EvmCallOperation;

  expect(op.opType).to.be.equal(OpType.EvmCall);
  expect(intent.settler).to.be.equal(context.settlers[0].address);
  expect(op.user).to.be.equal("0xSmartAccount");
  expect(op.chainId).to.be.equal(Chains.Optimism);

  expect(op.calls).to.have.lengthOf(1);
  expect(op.calls[0].target).to.be.equal(USDC);
  expect(op.calls[0].value).to.be.equal("0");
  const data = ERC20Interface.encodeFunctionData("approve", [
    "0xSpender",
    "1000000",
  ]);
  expect(op.calls[0].data).to.be.equal(data);

  expect(intent.feePayer).to.be.equal(context.user);
  expect(intent.maxFees).to.have.lengthOf(1);
  expect(intent.maxFees[0].token).to.be.equal("0xUSDC");
  expect(intent.maxFees[0].amount).to.be.equal("100000"); // 0.1 USDC
});
```

**2.2.2. Logs**

For example, if the function does:

```typescript
import { log } from "@mimicprotocol/lib-ts";

if (inputs.token == USDC) log.info("Function started");
else throw new Error("Token not supported");
```

Then, the test should be:

```typescript
import { runFunction } from "@mimicprotocol/test-ts";

describe("when the token is USDC", () => {
  it("executes properly", async () => {
    const result = await runFunction(/* 0xUSDC */);
    expect(result.success).to.be.true;

    expect(result.logs).to.have.lengthOf(1);
    expect(result.logs[0]).to.include("Function started");
  });
});

describe("when the token is not USDC", () => {
  it("throws an error", async () => {
    const result = await runFunction(/* 0xWETH */);
    expect(result.success).to.be.false;

    expect(result.logs).to.have.lengthOf(1);
    expect(result.logs[0]).to.include("Token not supported");
  });
});
```
