Matchstick Unit Testing Framework

repository·main·Indexed 42 days ago

https://github.com/limechain/matchstick

A Rust-based unit testing framework for The Graph protocol that allows developers to test mapping logic in a sandboxed environment. It provides a CLI for running tests, generating code coverage reports, and supports integration via OS-specific binaries or Docker. Version 0.6.0 requires the matchstick-as helper library for AssemblyScript unit tests and a local PostgreSQL installation.

Tokens
3K
Snippets
10
Records
15
Agent score
53%

What's inside Matchstick

  1. How Matchstick works

    main

    Matchstick is a unit testing framework for The Graph protocol written in Rust.

    Core Architecture

    • Wasm Wrapper: It acts as a wrapper for the generated WebAssembly module containing your mappings and unit tests.
    • Host Function Injection: The framework passes host function implementations down to the Wasm module so they can be used during tests and within mappings.
    • Proxying: It acts as a proxy for structs defined in the graph-node repository, ensuring all standard imports (and additional mocked imports) are correctly passed to the testing environment.
  2. Run Matchstick using OS-specific binaries

    main

    The recommended way to use Matchstick is via its OS-specific release binaries (macOS and Linux). These are automatically downloaded when you run the graph test command from your subgraph project's root folder.

    Usage

    • Run all tests: graph test
    • Run tests for a specific datasource: graph test <datasource_name> (e.g., graph test gravity)

    Prerequisites: PostgreSQL

    Matchstick requires a local PostgreSQL installation because graph-node depends on diesel. Failure to install PostgreSQL correctly may lead to unexpected errors.

    macOS

    1. Install PostgreSQL 14:
      brew install postgresql@14
    2. Create a symlink to the library:
      ln -sf /usr/local/opt/postgresql@14/lib/postgresql@14/libpq.5.dylib /usr/local/opt/postgresql/lib/libpq.5.dylib

    Linux

    Install PostgreSQL via your distribution's package manager:

    sudo apt install postgresql

    WSL (Windows Subsystem for Linux)

    If you encounter syntax errors or execution issues on WSL:

    1. Ensure you are using a modern Node.js version (Matchstick is confirmed working on v18.1.0). Avoid the default Ubuntu v10.19.0.
    2. Install libpq:
      sudo apt-get install libpq-dev
    3. Do not use graph test (the global graph-cli may be broken on WSL). Instead, use your local project-level instance via yarn test or npm run test. You can add a script to your package.json like this:
      "test": "graph test"
  3. Run Matchstick using Docker

    main

    If you cannot get the binary approach to work, you can run Matchstick in an Ubuntu-based Docker container. This is useful for quick trials or isolated environments.

    Setup

    1. Install Docker.
    2. Create a Dockerfile in your subgraph project root. Use the template from this file and replace <MATCHSTICK_VERSION> with your desired version (found in releases).
    3. Build the image:
      docker build -t matchstick .

    Running Tests

    Run the container by mounting your project directory. Use an absolute path for the source.

    Basic run:

    docker run -it --rm --mount type=bind,source=<absolute/path/to/project>,target=/matchstick matchstick

    Passing arguments (e.g., specific datasource or coverage): Use the ARGS environment variable to pass flags to Matchstick:

    docker run -e ARGS="gravity" -it --rm --mount type=bind,source=<absolute/path/to/project>,target=/matchstick matchstick

    Troubleshooting Docker

    • Permission Denied Error: If you encounter error from sender: failed to xattr node_modules/binary-install-raw/bin/binary-<platform>: permission denied during build, create a .dockerignore file in your root folder and add:
      node_modules/binary-install-raw/bin
    • Persistence: The command uses a bind mount, so changes made during the run will persist on your host machine, and you do not need to rebuild the image after code changes.
    docker build -t matchstick .
    
    docker run -it --rm --mount type=bind,source=<absolute/path/to/project>,target=/matchstick matchstick
  4. Install Matchstick dependencies

    main

    To use the test helper methods provided by Matchstick in your AssemblyScript unit tests, you must install the matchstick-as helper library as a development dependency in your subgraph project.

    Matchstick relies on matchstick-as (written in AssemblyScript) to be imported within your unit tests.

    yarn add --dev matchstick-as
  5. Configure Matchstick via matchstick.yaml

    main

    You can customize the default directory locations for tests, libraries, and the subgraph manifest by creating a matchstick.yaml configuration file in your project root.

    Configuration Keys

    • testsFolder: Path to your tests directory (defaults to ./tests).
    • libsFolder: Path to your node modules (defaults to ./node_modules).
    • manifestPath: Path to your subgraph.yaml file (defaults to ./subgraph.yaml).
  6. Configure Matchstick with matchstick.yaml

    main

    Matchstick relies on a matchstick.yaml configuration file to locate project assets. The following keys are used by the engine to set up the execution environment:

    • manifest_path: Path to the project manifest.
    • tests_path: Path to the directory containing test files.
    • libs_path: Path to the libraries used during compilation.

    Note: The schema location is automatically derived by looking for a schema file relative to the manifest_path using the internal parser::get_schema_location logic.

  7. Generate coverage reports with generate_coverage_report()

    main

    The generate_coverage_report() function analyzes the project's handlers to determine how many are exercised by existing test suites.

    It performs the following steps:

    1. Locates the .bin directory within the project's test location.
    2. Collects all .wasm files found in that directory (recursively).
    3. Converts these .wasm files into .wat (WebAssembly Text format) files using the wasm2wat tool located in the project's library directory.
    4. Parses the source code to identify all registered handlers.
    5. Scans the generated .wat files for patterns matching call.<handler_name> to verify if a handler was invoked during tests.
    6. Outputs a report showing the coverage percentage for each source file and a global coverage percentage.

    Output Format:

    • Per-source coverage: Test coverage: [percentage]% ([called]/[total] handlers).
    • Global coverage: Global test coverage: [percentage]% ([called]/[total] handlers).
    // This function is part of the internal coverage reporting logic
    // and can be invoked to run the coverage analysis.
    // It relies on the presence of wasm2wat in the project's library path.
    
    generate_coverage_report();
  8. Parse YAML files with `parse_yaml`

    main

    Use parse_yaml to read and parse a YAML file from a given path into a serde_yaml::Value.

    If the file cannot be read or contains invalid YAML, the function logs a critical or warning message and returns a Value::String containing an empty string ("").

    // Returns a serde_yaml::Value
    let yaml_value = parse_yaml("path/to/your/file.yaml");
  9. Get schema location with `get_schema_location`

    main

    Use get_schema_location to find the file path of the GraphQL schema defined in a subgraph.yaml file.

    It expects a schema object in the YAML containing a file key.

    Note: This function will panic if the schema key is missing or if the file key cannot be found within the schema object.

    // Returns the path to the schema file, e.g., "./schema.graphql"
    let schema_path = get_schema_location("subgraph.yaml");
  10. Collect supported templates with `collect_templates`

    main

    Use collect_templates to extract supported templates from a subgraph.yaml file.

    Matchstick only supports templates with the following kind values:

    • ethereum
    • ethereum/contract
    • file/ipfs

    If a template has a different kind, a warning is logged and it is skipped. Returns a Vec<(String, String)> where each tuple contains the template name and its kind.

    // Returns a Vec of (name, kind) for supported templates
    let templates = collect_templates("subgraph.yaml");
  11. Extract string values with `extract_string_or`

    main

    Use extract_string_or to safely retrieve a string value from a parsed YAML Value using a specific key. If the key is missing or the value cannot be parsed as a string, it falls back to the provided default value.

    // If 'testsFolder' exists and is a string, it returns it.
    // Otherwise, it returns "./tests".
    let folder = extract_string_or(&yaml_value, "testsFolder", "./tests".to_owned());
  12. Collect event and call handlers with `collect_handlers`

    main

    Use collect_handlers to scan a subgraph.yaml file and map each data source or template to its associated handler functions.

    It looks for dataSources and templates in the YAML, then traverses the mapping section of each to find eventHandlers and callHandlers. For each source, it extracts the name and a list of handler strings.

    Returns a HashMap<String, Vec<String>> where the key is the source name and the value is a vector of handler function names.

    // Returns a HashMap mapping source names to their handler functions
    let handlers = collect_handlers("subgraph.yaml");
    
    // Example output structure:
    // {
    //   "Gravity": ["handleNewGravatar", "handleCreateGravatar"],
    //   "GraphTokenLockWallet": ["handleTokensReleased"]
    // }