# 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");
  });
});
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mimic.fi/developers/tests.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
