Testing

Complete testing framework and best practices for MemeTrade smart contracts, APIs, and integrations. Learn how to test locally, on testnets, and in production environments.

Testing Environment Setup

Local Development Environment

Prerequisites:

# Install Foundry (recommended)
curl -L https://foundry.paradigm.xyz | bash
foundryup

# Or install Hardhat
npm install -g hardhat

# Install Node.js dependencies
npm install ethers dotenv @nomiclabs/hardhat-ethers

Environment Configuration:

# .env file
BASE_SEPOLIA_RPC_URL="https://sepolia.base.org"
BASE_MAINNET_RPC_URL="https://mainnet.base.org"
PRIVATE_KEY="your-test-private-key"
MEMETRADE_API_KEY="your-api-key"
ETHERSCAN_API_KEY="your-etherscan-key"

Foundry Testing Setup

foundry.toml Configuration:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.19"
optimizer = true
optimizer_runs = 200
via_ir = true

[profile.default.fuzz]
runs = 1000

[profile.default.invariant]
runs = 256
depth = 15
fail_on_revert = false

[rpc_endpoints]
base_sepolia = "${BASE_SEPOLIA_RPC_URL}"
base_mainnet = "${BASE_MAINNET_RPC_URL}"

[etherscan]
base_sepolia = { key = "${ETHERSCAN_API_KEY}", url = "https://api-sepolia.basescan.org/api" }
base = { key = "${ETHERSCAN_API_KEY}", url = "https://api.basescan.org/api" }

Smart Contract Testing

Unit Testing with Foundry

Basic Test Structure:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import "../src/Factory.sol";
import "../src/templates/Template.sol";

contract FactoryTest is Test {
    Factory public factory;
    address public owner = address(0x1);
    address public user = address(0x2);

    event TokenCreated(
        address indexed token,
        address indexed creator,
        string name,
        string symbol
    );

    function setUp() public {
        vm.startPrank(owner);
        factory = new Factory();
        vm.stopPrank();
    }

    function testCreateToken() public {
        vm.startPrank(user);
        vm.deal(user, 1 ether);

        // Expect TokenCreated event
        vm.expectEmit(true, true, false, true);
        emit TokenCreated(
            address(0), // We don't know the address yet
            user,
            "Test Token",
            "TEST"
        );

        address tokenAddress = factory.createToken{value: 0.001 ether}(
            "Test Token",
            "TEST",
            "A test token",
            "https://example.com/image.png",
            250, // 2.5% creator fee
            0    // Basic template
        );

        // Verify token was created
        assertNotEq(tokenAddress, address(0));

        // Verify token properties
        Template token = Template(tokenAddress);
        assertEq(token.name(), "Test Token");
        assertEq(token.symbol(), "TEST");
        assertEq(token.creator(), user);

        vm.stopPrank();
    }

    function testCreateTokenInsufficientFee() public {
        vm.startPrank(user);
        vm.deal(user, 0.0005 ether); // Insufficient fee

        vm.expectRevert("Insufficient creation fee");
        factory.createToken{value: 0.0005 ether}(
            "Test Token",
            "TEST",
            "A test token",
            "https://example.com/image.png",
            250,
            0
        );

        vm.stopPrank();
    }
}

Advanced Testing Patterns:

contract TemplateTest is Test {
    Template public token;
    address public creator = address(0x1);
    address public trader = address(0x2);

    function setUp() public {
        vm.startPrank(creator);
        token = new Template();
        token.initialize(
            "Test Token",
            "TEST",
            "Description",
            "https://image.url",
            creator,
            250 // 2.5% fee
        );
        vm.stopPrank();
    }

    function testFuzzTokenCreation(
        string memory name,
        string memory symbol,
        uint256 creatorFee
    ) public {
        // Bound inputs to valid ranges
        vm.assume(bytes(name).length > 0 && bytes(name).length <= 50);
        vm.assume(bytes(symbol).length > 0 && bytes(symbol).length <= 10);
        creatorFee = bound(creatorFee, 0, 500); // 0-5%

        Template testToken = new Template();
        testToken.initialize(
            name,
            symbol,
            "Test description",
            "https://test.com/image.png",
            creator,
            creatorFee
        );

        assertEq(testToken.name(), name);
        assertEq(testToken.symbol(), symbol);
        assertEq(testToken.creatorFee(), creatorFee);
    }

    function testInvariantTokenSupply() public {
        // Token supply should never exceed max supply
        uint256 maxSupply = token.MAX_SUPPLY();
        assertTrue(token.totalSupply() <= maxSupply);

        // Creator should always own initial supply
        assertTrue(token.balanceOf(creator) > 0);
    }
}

Fuzz Testing

Property-Based Testing:

contract LiquidityInvariantTest is Test {
    using stdInvariant for StdInvariantFuzzInterface;

    LiquidityMiningHelper public liquidityMining;
    ERC20 public tokenA;
    ERC20 public tokenB;

    function setUp() public {
        tokenA = new MockERC20("Token A", "TKNA");
        tokenB = new MockERC20("Token B", "TKNB");
        liquidityMining = new LiquidityMiningHelper();

        // Add target contracts for invariant testing
        targetContract(address(liquidityMining));
        targetContract(address(tokenA));
        targetContract(address(tokenB));
    }

    function invariant_liquidityNeverNegative() public {
        // Liquidity should never be negative
        uint256 totalLiquidity = liquidityMining.getTotalLiquidity();
        assertTrue(totalLiquidity >= 0);
    }

    function invariant_rewardsDistribution() public {
        // Total rewards distributed should not exceed allocated rewards
        uint256 totalDistributed = liquidityMining.getTotalDistributedRewards();
        uint256 totalAllocated = liquidityMining.getTotalAllocatedRewards();
        assertTrue(totalDistributed <= totalAllocated);
    }
}

Integration Testing

Cross-Contract Testing:

contract IntegrationTest is Test {
    Factory public factory;
    LiquidityMiningHelper public liquidityMining;
    Governance public governance;

    address public owner = address(0x1);
    address public user1 = address(0x2);
    address public user2 = address(0x3);

    function setUp() public {
        vm.startPrank(owner);

        // Deploy all contracts
        factory = new Factory();
        liquidityMining = new LiquidityMiningHelper();
        governance = new Governance();

        // Set up relationships
        factory.setLiquidityMiningHelper(address(liquidityMining));
        liquidityMining.setGovernance(address(governance));

        vm.stopPrank();
    }

    function testFullTokenLifecycle() public {
        // 1. Create token
        vm.startPrank(user1);
        vm.deal(user1, 1 ether);

        address tokenAddress = factory.createToken{value: 0.001 ether}(
            "Integration Token",
            "INT",
            "Integration test token",
            "https://example.com/int.png",
            300, // 3% creator fee
            0    // Basic template
        );

        Template token = Template(tokenAddress);
        vm.stopPrank();

        // 2. Add liquidity
        vm.startPrank(user2);
        vm.deal(user2, 2 ether);

        // Mint tokens for liquidity provision
        token.mint{value: 1 ether}(user2, 1000 ether);

        // Add liquidity to pool
        liquidityMining.addLiquidity(
            tokenAddress,
            address(0), // ETH
            1000 ether,  // Token amount
            1 ether      // ETH amount
        );

        vm.stopPrank();

        // 3. Verify liquidity mining rewards
        uint256 userLPBalance = liquidityMining.getUserLPBalance(user2, tokenAddress);
        assertTrue(userLPBalance > 0);

        // 4. Fast forward time and claim rewards
        vm.warp(block.timestamp + 1 days);

        vm.startPrank(user2);
        uint256 pendingRewards = liquidityMining.getPendingRewards(user2, tokenAddress);
        assertTrue(pendingRewards > 0);

        liquidityMining.claimRewards(tokenAddress);
        vm.stopPrank();

        // 5. Verify governance voting power
        uint256 votingPower = governance.getVotingPower(user2);
        assertTrue(votingPower > 0); // Should have voting power from LP tokens
    }
}

API Testing

REST API Testing

Jest Test Setup:

// package.json
{
  "devDependencies": {
    "jest": "^29.0.0",
    "supertest": "^6.0.0",
    "@types/jest": "^29.0.0",
    "@types/supertest": "^2.0.0"
  },
  "scripts": {
    "test": "jest",
    "test:api": "jest --testMatch='**/*.api.test.js'"
  }
}

API Test Examples:

// tests/api.test.js
const request = require("supertest");

const BASE_URL =
  process.env.API_BASE_URL || "https://api-sepolia.memetrade.com";
const API_KEY = process.env.MEMETRADE_API_KEY;

describe("MemeTrade API", () => {
  const headers = {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  };

  describe("GET /api/v1/tokens", () => {
    test("should return paginated token list", async () => {
      const response = await request(BASE_URL)
        .get("/api/v1/tokens?page=1&limit=10")
        .set(headers)
        .expect(200);

      expect(response.body).toHaveProperty("tokens");
      expect(response.body).toHaveProperty("pagination");
      expect(response.body.tokens).toBeInstanceOf(Array);
      expect(response.body.tokens.length).toBeLessThanOrEqual(10);

      // Verify token structure
      if (response.body.tokens.length > 0) {
        const token = response.body.tokens[0];
        expect(token).toHaveProperty("address");
        expect(token).toHaveProperty("name");
        expect(token).toHaveProperty("symbol");
        expect(token).toHaveProperty("price");
        expect(token).toHaveProperty("marketCap");
      }
    });

    test("should handle invalid pagination", async () => {
      await request(BASE_URL)
        .get("/api/v1/tokens?page=-1&limit=1000")
        .set(headers)
        .expect(400);
    });
  });

  describe("GET /api/v1/tokens/:address", () => {
    test("should return token details", async () => {
      // Use a known test token address
      const tokenAddress = "0x742d35Cc62D51C846fDa67F9e3c4b65e45E2F4Aa";

      const response = await request(BASE_URL)
        .get(`/api/v1/tokens/${tokenAddress}`)
        .set(headers)
        .expect(200);

      expect(response.body).toHaveProperty("address", tokenAddress);
      expect(response.body).toHaveProperty("name");
      expect(response.body).toHaveProperty("symbol");
      expect(response.body).toHaveProperty("totalSupply");
      expect(response.body).toHaveProperty("creator");
    });

    test("should return 404 for non-existent token", async () => {
      const fakeAddress = "0x0000000000000000000000000000000000000000";

      await request(BASE_URL)
        .get(`/api/v1/tokens/${fakeAddress}`)
        .set(headers)
        .expect(404);
    });
  });

  describe("POST /api/v1/trading/quote", () => {
    test("should return valid quote", async () => {
      const quoteRequest = {
        tokenIn: "0x0000000000000000000000000000000000000000", // ETH
        tokenOut: "0x742d35Cc62D51C846fDa67F9e3c4b65e45E2F4Aa",
        amountIn: "1000000000000000000", // 1 ETH
        slippage: 0.01,
      };

      const response = await request(BASE_URL)
        .post("/api/v1/trading/quote")
        .set(headers)
        .send(quoteRequest)
        .expect(200);

      expect(response.body).toHaveProperty("amountOut");
      expect(response.body).toHaveProperty("priceImpact");
      expect(response.body).toHaveProperty("minimumReceived");
      expect(response.body).toHaveProperty("route");

      // Verify numeric fields
      expect(typeof response.body.amountOut).toBe("string");
      expect(typeof response.body.priceImpact).toBe("number");
      expect(response.body.priceImpact).toBeGreaterThanOrEqual(0);
    });
  });
});

Load Testing:

// tests/load.test.js
const request = require("supertest");

describe("API Load Testing", () => {
  test("should handle concurrent requests", async () => {
    const promises = [];
    const concurrentRequests = 50;

    for (let i = 0; i < concurrentRequests; i++) {
      promises.push(
        request(BASE_URL).get("/api/v1/tokens/trending").set(headers)
      );
    }

    const responses = await Promise.allSettled(promises);

    // Most requests should succeed
    const successCount = responses.filter(
      (r) => r.status === "fulfilled" && r.value.status === 200
    ).length;

    expect(successCount).toBeGreaterThan(concurrentRequests * 0.8); // 80% success rate
  });
});

WebSocket Testing

WebSocket Test Framework:

// tests/websocket.test.js
const WebSocket = require("ws");

describe("WebSocket API (Planned)", () => {
  // Test cases for WebSocket API will be added once the API is implemented.
});

Frontend Testing

React Component Testing

Component Test Setup:

// setupTests.js
import "@testing-library/jest-dom";
import { TextEncoder, TextDecoder } from "util";

global.TextEncoder = TextEncoder;
global.TextDecoder = TextDecoder;

// Mock WebSocket
global.WebSocket = jest.fn(() => ({
  send: jest.fn(),
  close: jest.fn(),
  addEventListener: jest.fn(),
  removeEventListener: jest.fn(),
}));

Component Tests:

// TokenPrice.test.tsx
import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import { TokenPrice } from "../components/TokenPrice";

// Mock the SDK
jest.mock("@memetrade/sdk", () => ({
  MemeTrade: jest.fn().mockImplementation(() => ({
    tokens: {
      getStats: jest.fn().mockResolvedValue({
        price: 0.000123,
        volume24h: 50000,
        marketCap: 123456,
      }),
    },
  })),
}));

describe("TokenPrice Component", () => {
  const tokenAddress = "0x742d35Cc62D51C846fDa67F9e3c4b65e45E2F4Aa";

  test("displays token price", async () => {
    render(<TokenPrice tokenAddress={tokenAddress} />);

    // Initially shows loading
    expect(screen.getByText("Loading price...")).toBeInTheDocument();

    // Wait for price to load
    await waitFor(() => {
      expect(screen.getByText("$0.000123")).toBeInTheDocument();
    });
  });

  test("handles error state", async () => {
    // Mock error
    const { MemeTrade } = require("@memetrade/sdk");
    MemeTrade.mockImplementation(() => ({
      tokens: {
        getStats: jest.fn().mockRejectedValue(new Error("API Error")),
      },
    }));

    render(<TokenPrice tokenAddress={tokenAddress} />);

    await waitFor(() => {
      expect(screen.getByText("Price unavailable")).toBeInTheDocument();
    });
  });
});

E2E Testing with Playwright

Playwright Setup:

// playwright.config.js
module.exports = {
  testDir: "./tests/e2e",
  timeout: 30000,
  use: {
    baseURL: "http://localhost:3000",
    headless: true,
    screenshot: "only-on-failure",
  },
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
    { name: "webkit", use: { ...devices["Desktop Safari"] } },
  ],
};

E2E Test Examples:

// tests/e2e/token-creation.spec.js
import { test, expect } from "@playwright/test";

test.describe("Token Creation Flow", () => {
  test("should create token successfully", async ({ page }) => {
    await page.goto("/create");

    // Fill form
    await page.fill('[data-testid="token-name"]', "Test Token");
    await page.fill('[data-testid="token-symbol"]', "TEST");
    await page.fill(
      '[data-testid="token-description"]',
      "A test token for E2E testing"
    );

    // Upload image
    await page.setInputFiles('[data-testid="token-image"]', "test-image.png");

    // Set creator fee
    await page.fill('[data-testid="creator-fee"]', "2.5");

    // Submit form
    await page.click('[data-testid="create-token-button"]');

    // Wait for wallet connection (if needed)
    // This would depend on your wallet integration

    // Verify success
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
    await expect(page.locator('[data-testid="token-address"]')).toBeVisible();
  });

  test("should show validation errors", async ({ page }) => {
    await page.goto("/create");

    // Try to submit empty form
    await page.click('[data-testid="create-token-button"]');

    // Check for validation errors
    await expect(page.locator('[data-testid="name-error"]')).toContainText(
      "Name is required"
    );
    await expect(page.locator('[data-testid="symbol-error"]')).toContainText(
      "Symbol is required"
    );
  });
});

test.describe("Trading Flow", () => {
  test("should execute trade", async ({ page }) => {
    const tokenAddress = "0x742d35Cc62D51C846fDa67F9e3c4b65e45E2F4Aa";

    await page.goto(`/token/${tokenAddress}`);

    // Check token page loads
    await expect(page.locator('[data-testid="token-name"]')).toBeVisible();
    await expect(page.locator('[data-testid="token-price"]')).toBeVisible();

    // Enter trade amount
    await page.fill('[data-testid="trade-amount"]', "0.1");

    // Click buy button
    await page.click('[data-testid="buy-button"]');

    // Confirm transaction (mock wallet interaction)
    await page.click('[data-testid="confirm-trade"]');

    // Wait for transaction confirmation
    await expect(page.locator('[data-testid="trade-success"]')).toBeVisible({
      timeout: 30000,
    });
  });
});

Performance Testing

Contract Gas Optimization

Gas Testing:

contract GasTest is Test {
    Factory public factory;

    function setUp() public {
        factory = new Factory();
    }

    function testTokenCreationGas() public {
        uint256 gasBefore = gasleft();

        factory.createToken{value: 0.001 ether}(
            "Gas Test Token",
            "GAS",
            "Testing gas consumption",
            "https://example.com/gas.png",
            250,
            0
        );

        uint256 gasUsed = gasBefore - gasleft();

        // Assert gas usage is within expected range
        assertTrue(gasUsed < 2_000_000, "Token creation uses too much gas");

        console.log("Gas used for token creation:", gasUsed);
    }

    function testBatchOperationGas() public {
        // Test gas efficiency of batch operations vs individual operations

        // Individual operations
        uint256 gasIndividual = 0;
        uint256 gasBefore = gasleft();

        // Operation 1
        factory.updateFee(100);
        uint256 gasAfter1 = gasleft();
        gasIndividual += gasBefore - gasAfter1;

        // Operation 2
        gasBefore = gasleft();
        factory.updateCreationFee(0.002 ether);
        uint256 gasAfter2 = gasleft();
        gasIndividual += gasBefore - gasAfter2;

        // Batch operation
        gasBefore = gasleft();
        factory.batchUpdate(100, 0.002 ether);
        uint256 gasBatch = gasBefore - gasleft();

        // Batch should be more efficient
        assertTrue(gasBatch < gasIndividual, "Batch operation should use less gas");

        console.log("Individual operations gas:", gasIndividual);
        console.log("Batch operation gas:", gasBatch);
        console.log("Gas savings:", gasIndividual - gasBatch);
    }
}

Load Testing Scripts

Artillery Load Testing:

# load-test.yml
config:
  target: "https://api-sepolia.memetrade.com"
  phases:
    - duration: 60
      arrivalRate: 10
    - duration: 120
      arrivalRate: 50
    - duration: 60
      arrivalRate: 100
  headers:
    Authorization: "Bearer {{ $processEnvironment.API_KEY }}"
    Content-Type: "application/json"

scenarios:
  - name: "Get token list"
    weight: 40
    flow:
      - get:
          url: "/api/v1/tokens?page={{ $randomInt(1, 10) }}&limit=20"

  - name: "Get token details"
    weight: 30
    flow:
      - get:
          url: "/api/v1/tokens/{{ $randomString(42) }}"

  - name: "Get trading quote"
    weight: 20
    flow:
      - post:
          url: "/api/v1/trading/quote"
          json:
            tokenIn: "0x0000000000000000000000000000000000000000"
            tokenOut: "0x742d35Cc62D51C846fDa67F9e3c4b65e45E2F4Aa"
            amountIn: "{{ $randomInt(1000000000000000000, 10000000000000000000) }}"
            slippage: 0.01

  - name: "WebSocket connection"
    weight: 10
    engine: ws
    flow:
      - connect:
          target: "wss://api-sepolia.memetrade.com/ws"
      - send:
          payload: |
            {
              "type": "subscribe",
              "channel": "prices",
              "tokens": ["0x742d35Cc62D51C846fDa67F9e3c4b65e45E2F4Aa"]
            }
      - think: 30

Testnet Testing

Base Sepolia Testing

Faucet Integration:

// scripts/setup-testnet.js
const { ethers } = require("ethers");

async function setupTestnetAccount() {
  const provider = new ethers.providers.JsonRpcProvider(
    process.env.BASE_SEPOLIA_RPC_URL
  );

  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

  console.log("Test account:", wallet.address);

  // Check ETH balance
  const balance = await provider.getBalance(wallet.address);
  console.log("ETH balance:", ethers.utils.formatEther(balance));

  if (balance.lt(ethers.utils.parseEther("0.1"))) {
    console.log("Low balance! Get testnet ETH from:");
    console.log("https://bridge.base.org/");
    console.log("https://faucet.quicknode.com/base/sepolia");
  }

  // Test factory interaction
  const factory = new ethers.Contract(
    "0x742d35Cc62D51C846fDa67F9e3c4b65e45E2F4Aa",
    [
      "function createToken(string,string,string,string,uint256,uint256) payable returns(address)",
    ],
    wallet
  );

  try {
    const tx = await factory.createToken(
      "Test Token",
      "TEST",
      "Testnet testing token",
      "https://example.com/test.png",
      250, // 2.5% fee
      0, // Basic template
      { value: ethers.utils.parseEther("0.001") }
    );

    console.log("Token creation tx:", tx.hash);
    const receipt = await tx.wait();
    console.log("Token created at block:", receipt.blockNumber);
  } catch (error) {
    console.error("Token creation failed:", error.message);
  }
}

setupTestnetAccount().catch(console.error);

Automated Testing Pipeline:

// scripts/test-pipeline.js
const { execSync } = require("child_process");

async function runTestPipeline() {
  console.log("๐Ÿงช Starting MemeTrade Test Pipeline...\n");

  try {
    // 1. Smart contract tests
    console.log("๐Ÿ“‹ Running smart contract tests...");
    execSync("forge test", { stdio: "inherit" });
    console.log("โœ… Smart contract tests passed\n");

    // 2. API tests
    console.log("๐Ÿ”— Running API tests...");
    execSync("npm run test:api", { stdio: "inherit" });
    console.log("โœ… API tests passed\n");

    // 3. Frontend tests
    console.log("๐ŸŽจ Running frontend tests...");
    execSync("npm run test:frontend", { stdio: "inherit" });
    console.log("โœ… Frontend tests passed\n");

    // 4. E2E tests
    console.log("๐ŸŽญ Running E2E tests...");
    execSync("npx playwright test", { stdio: "inherit" });
    console.log("โœ… E2E tests passed\n");

    // 5. Load tests (optional)
    if (process.env.RUN_LOAD_TESTS === "true") {
      console.log("โšก Running load tests...");
      execSync("artillery run load-test.yml", { stdio: "inherit" });
      console.log("โœ… Load tests passed\n");
    }

    console.log("๐ŸŽ‰ All tests passed!");
  } catch (error) {
    console.error("โŒ Test pipeline failed:", error.message);
    process.exit(1);
  }
}

runTestPipeline();

CI/CD Testing

GitHub Actions Workflow

# .github/workflows/test.yml
name: Test Suite

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  smart-contracts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          submodules: recursive

      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1

      - name: Run smart contract tests
        run: |
          cd memetrade
          forge test --gas-report

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.json

  api-tests:
    runs-on: ubuntu-latest
    services:
      redis:
        image: redis:alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "18"

      - name: Install dependencies
        run: npm ci

      - name: Run API tests
        env:
          MEMETRADE_API_KEY: ${{ secrets.TEST_API_KEY }}
          BASE_SEPOLIA_RPC_URL: ${{ secrets.BASE_SEPOLIA_RPC_URL }}
        run: npm run test:api

  frontend-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "18"

      - name: Install dependencies
        run: npm ci

      - name: Run frontend tests
        run: npm run test:frontend

      - name: Build application
        run: npm run build

  e2e-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "18"

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright
        run: npx playwright install --with-deps

      - name: Run E2E tests
        env:
          MEMETRADE_API_KEY: ${{ secrets.TEST_API_KEY }}
        run: npx playwright test

      - name: Upload test results
        uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

Best Practices

Testing Strategies

Test Pyramid:

    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚  E2E Tests  โ”‚  (Few, High-level, Slow)
    โ”‚     10%     โ”‚
    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    โ”‚ Integration โ”‚  (Some, Medium-level, Medium)
    โ”‚ Tests 20%   โ”‚
    โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    โ”‚ Unit Tests  โ”‚  (Many, Low-level, Fast)
    โ”‚    70%      โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Code Coverage Goals:

  • Smart contracts: >95%

  • APIs: >90%

  • Frontend components: >80%

  • E2E critical paths: 100%

Security Testing

Common Vulnerabilities to Test:

// Test for reentrancy
function testReentrancyProtection() public {
    vm.expectRevert("ReentrancyGuard: reentrant call");
    // Attempt reentrancy attack
}

// Test for overflow/underflow
function testSafeArithmetic() public {
    // Test edge cases with SafeMath
}

// Test access controls
function testOnlyOwnerFunctions() public {
    vm.expectRevert("Ownable: caller is not the owner");
    // Try calling owner-only function as non-owner
}

// Test input validation
function testInputSanitization() public {
    vm.expectRevert("Invalid input");
    // Pass malicious input
}

Performance Benchmarks

Target Metrics:

  • API response time: <100ms (95th percentile)

  • WebSocket message latency: <50ms

  • Contract deployment: <500k gas

  • Token creation: <300k gas

  • Trade execution: <200k gas


Ready to test your integration? Start with the basic test setup and gradually add more comprehensive testing as your integration grows. Join our Discord #testing-support channel for help with specific testing scenarios.

Next Steps:

Last updated