Search by

masterjus / behat-hypervel-extension

masterjus

Behat extension for testing Hypervel applications: boots the app, injects it into contexts and provides ready-made steps for HTTP, JSON, database, config and fixtures.

Package info

github.com/masterjus/behat-hypervel-extension

pkg:composer/masterjus/behat-hypervel-extension

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-11-05 12:29 UTC

This package is auto-updated.

Last update: 2026-11-05 12:33:03 UTC


README

Behat extension for testing Hypervel applications.

It boots your application once per test run, injects it into your contexts and ships ready-made steps for HTTP requests, JSON responses, database assertions, config overrides and database fixtures.

Requirements

  • PHP 8.2+ with the Swoole extension
  • Hypervel 0.3
  • Behat 3.10+

Installation

1. Install the package

composer require --dev masterjus/behat-hypervel-extension

2. Create the testing environment file

Tests run with their own .env file, tests/.env by default. Before booting the application the extension replaces all the values loaded from the root .env with the values from it, so the tests never touch the database from your main .env.

Create tests/.env from your main .env. The values are not merged: everything the application needs must be in tests/.env. At least:

APP_ENV=testing
APP_URL=http://localhost

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=app_testing
DB_USERNAME=app
DB_PASSWORD=secret

APP_URL is sent as the Host header of every request made by the HTTP steps.

The database from tests/.env is recreated with migrate:fresh at the beginning of every run (unless refresh_database is false), so never point it at a database with data you need. If tests/.env is missing, the run stops with an error instead of falling back to the root .env.

Keep tests/.env out of git and commit templates instead, for example tests/.env.example for developers and tests/.env.ci for CI, which copies it before running the tests:

cp tests/.env.ci tests/.env
vendor/bin/behat

3. Enable the extension

# behat.yml
default:
  suites:
    default:
      paths: [ "%paths.base%/tests/Feature" ]
      contexts:
        - Tests\Behat\FeatureContext
  extensions:
    BehatHypervel: ~

4. Autoload the contexts

Behat contexts are regular classes, so add their directory to autoload-dev in your composer.json and run composer dump-autoload:

"autoload-dev": {
    "psr-4": {
        "Tests\\": "tests/",
        "Tests\\Behat\\": "tests/Behat/"
    }
}

A project layout with the defaults:

project/
├── bootstrap/app.php
├── tests/
│   ├── Behat/FeatureContext.php
│   ├── Feature/users.feature
│   ├── fixtures/users.fixture
│   ├── .env            (not in git)
│   └── .env.example
├── behat.yml
└── composer.json

Configuration

default:
  extensions:
    BehatHypervel:
      bootstrap_path: bootstrap/app.php
      env_path: tests
      refresh_database: true
Option Default Description
bootstrap_path bootstrap/app.php File that returns the application, relative to the project root.
env_path tests Directory with the .env file for tests. ~ keeps the .env from the project root.
refresh_database true Run migrate:fresh once at the beginning of the test run.

Before requiring bootstrap_path the extension does what artisan does: defines BASE_PATH and SWOOLE_HOOK_FLAGS and initializes Hypervel\Foundation\ClassLoader. So bootstrap/app.php from a fresh Hypervel project works as is, no separate bootstrap file for Behat is needed.

The application is not bound to the testing database connection resolver of Hypervel: it keeps one connection for all coroutines, while the application under test may run queries in parallel ones. Connections come from the regular pool.

Context

A context receives the application when it implements BehatHypervel\Context\HypervelAwareContext. Add the traits with the steps you need:

<?php

namespace Tests\Behat;

use BehatHypervel\Context\Concerns\InteractsWithDatabase;
use BehatHypervel\Context\Concerns\LoadsFixtures;
use BehatHypervel\Context\Concerns\MakesHttpRequests;
use BehatHypervel\Context\Concerns\WorksWithAppConfig;
use BehatHypervel\Context\HypervelAwareContext;
use Hypervel\Foundation\Contracts\Application;

class FeatureContext implements HypervelAwareContext
{
    use InteractsWithDatabase;
    use LoadsFixtures;
    use MakesHttpRequests;
    use WorksWithAppConfig;

    protected Application $app;

    public function setApp(Application $app): void
    {
        $this->app = $app;
    }
}

The application is created once and shared between scenarios. Config values changed with the steps below are restored after each scenario.

Steps

HTTP

Given the "Authorization" header is "Bearer token"
Given the "Accept-Language" header is absent
When I send GET request to "/api/users"
When I send POST request to "/api/users" with JSON body:
  """
  {"name": "John"}
  """
Then status code must be 200
Then the JSON response must be exact:
  """
  {"id": 1, "name": "John"}
  """
Then the JSON response should be similar:
Then the JSON response should be:
  """
  {"id": 1, "name": "John"}
  """
Then the JSON response property "data.0.id" should be "1"
Then the JSON response should have fragment:
Then the JSON response should miss fragment:

Supported methods: GET, POST, DELETE, HEAD, OPTIONS.

Headers set with the header is steps apply to the next request only and are cleared after it.

How the JSON steps compare the response:

Step Keys order Lists order Types Extra data in the response
must be exact ignored matters strict fails
should be similar ignored ignored strict fails
should be ignored matters loose, 1 equals "1" fails
should have fragment — — strict allowed

property ... should be takes a dot path and casts the expected value to the type of the actual one: "1" matches 1 and 1.0, "true", "false" and "null" match the corresponding values.

Database

Then the table "users" should have record with:
  """
  {"email": "john@example.com"}
  """
Then the table "users" should not have record with:
Given I execute SQL statement:
  """
  UPDATE users SET deleted_at = NOW() WHERE id = 1
  """

Config

Given I set config "app.debug" to value "true"
Given I set config "services.mailer" to empty value
Given I replace config "services.payments" with:
  """
  {"driver": "fake"}
  """

Fixtures

Given data from the "users" fixture is loaded

Fixtures are loaded from tests/fixtures/<name>.fixture. The directory can be changed with setFixtureDir(). A fixture uses Gherkin syntax:

Fixture: Users
  Optional description

  Table: users
    Data:
      | name | email            | deleted_at |
      | John | john@example.com |            |

  Materialized View: users_summary

Empty cells are inserted as NULL. After each scenario the tables from the loaded fixtures are truncated and the materialized views are reset.

LoadsFixtures needs InteractsWithDatabase in the same context.

Database Tables cleanup Materialized views
PostgreSQL TRUNCATE ... RESTART IDENTITY CASCADE Supported
MySQL TRUNCATE for each table with FOREIGN_KEY_CHECKS disabled Not supported

Other drivers can be added by implementing BehatHypervel\Fixtures\Database\DatabaseAdapter and overriding getFixtureDatabaseAdapter() in your context.

Running

vendor/bin/behat

License

MIT