imbo/behat-api-extension
Behat 3 extension for testing JSON-based APIs. Simplifies making HTTP requests, asserting responses, and validating JSON payloads in your scenarios. Inspired by behat/web-api-extension and built for API testing workflows like Imbo.
Installation:
composer require --dev imbo/behat-api-extension
Add to behat.yml:
extensions:
Imbo\BehatApiExtension\Extension: ~
First Use Case: Test a simple GET request with assertions:
Feature: API Test
Scenario: Fetch user data
Given I send a GET request to "/api/users/1"
Then the response status code should be 200
And the response should match JSON:
"""
{
"id": 1,
"name": "John Doe"
}
"""
Key Files:
features/bootstrap/FeatureContext.php (default context)behat.yml (configuration)Request-Response Cycles:
Given I send a POST request to "/api/posts" with:
"""
{
"title": "Test Post",
"body": "Content..."
}
"""
Then the response status code should be 201
And the response should contain JSON path "$.id"
Authentication:
Given I send a GET request to "/api/protected" with headers:
| Authorization | Bearer token123 |
File Uploads:
Given I send a POST request to "/api/upload" with multipart form data:
| file | @path/to/file.jpg |
Dynamic Data:
// In FeatureContext.php
public function setDynamicData($key, $value) {
$this->dynamicData[$key] = $value;
}
Given I set dynamic data "userId" to 1
And I send a GET request to "/api/users/{userId}"
Hooks for Setup/Teardown:
// In FeatureContext.php
protected function getBaseUrl() {
return getenv('API_BASE_URL') ?: 'http://localhost:8000';
}
Laravel API Testing:
Combine with Laravel’s Http facade for pre-request logic:
use Illuminate\Support\Facades\Http;
public function beforeScenario(Scenario $scenario) {
Http::fake([
'api/users/*' => Http::response(['id' => 1], 200),
]);
}
Environment-Specific Config:
# behat.yml
suites:
default:
contexts:
- FeatureContext:
base_url: "%env(API_URL)%"
Custom Matchers:
Extend Imbo\BehatApiExtension\Context\JsonPathMatcher for domain-specific assertions.
PHP Version Mismatch:
composer.json constraints.v5.x for PHP 8.1 or v3.x for PHP 7.4 if needed.JSON Path Syntax:
$.user.name) or bracket notation ($['user']['name']).*) in paths—use exact matches or custom matchers.Multipart Form Data:
multipart/form-data with JSON bodies. The extension auto-detects but may fail silently.Context Initialization:
FeatureContext extends Imbo\BehatApiExtension\Context\ApiContext:
use Imbo\BehatApiExtension\Context\ApiContext;
class FeatureContext extends ApiContext { ... }
Caching Responses:
behat.yml if tests flake due to stale responses:
extensions:
Imbo\BehatApiExtension\Extension:
cache: false
Enable Verbose Output:
behat --verbose
Or configure in behat.yml:
default:
suites:
default:
filters:
tags: ~
extensions:
Behat\MinkExtension:
base_url: null
goutte: ~
selenium2: ~
Imbo\BehatApiExtension\Extension:
debug: true
Inspect Raw Responses:
Then dump the response
Outputs raw HTTP response (headers + body) to console.
Mock External APIs:
Use Laravel’s Http::fake() or Mockery in beforeScenario() to isolate tests.
Custom HTTP Clients:
Override createClient() in ApiContext to use Guzzle with custom middleware:
protected function createClient() {
$client = new \GuzzleHttp\Client([
'timeout' => 10,
'headers' => ['X-Custom-Header' => 'value'],
]);
return $client;
}
Dynamic Headers:
Given I set header "Authorization" to "Bearer {token}"
Combine with Laravel’s Str::random() or config() for dynamic values.
Pre/Post-Request Hooks:
public function beforeRequest(Request $request) {
$request->setHeader('X-Test-ID', uniqid());
}
Custom Assertions:
Extend JsonPathMatcher for domain-specific logic (e.g., date validation):
class CustomMatcher extends \Imbo\BehatApiExtension\Context\JsonPathMatcher {
public function matchDate($expected, $actual) {
return strtotime($expected) === strtotime($actual);
}
}
Register in behat.yml:
extensions:
Imbo\BehatApiExtension\Extension:
matchers:
date: CustomMatcher
How can I help you explore Laravel packages today?