Multi-Spec OpenAPI Support
BerryCrush supports working with multiple OpenAPI specifications in a single test suite. This is useful when your API is split across multiple spec files or when you need to test different API versions.
Configuring Multiple Specs
Default Spec
The primary spec is configured via getOpenApiSpec() in your bindings class:
class MyBindings : BerryCrushBindings {
override fun getOpenApiSpec(): String? {
return "petstore.yaml" // Default spec
}
}
Additional Named Specs
To register additional specs, override the getAdditionalSpecs() method:
@Component
@Lazy
public class PetstoreBindings implements BerryCrushBindings {
@LocalServerPort
private int port;
@Override
public Map<String, Object> getBindings() {
return Map.of("baseUrl", "http://localhost:" + port + "/api/v1");
}
@Override
public String getOpenApiSpec() {
return "petstore.yaml"; // Default spec for pet operations
}
@Override
public Map<String, String> getAdditionalSpecs() {
return Map.of(
"auth", "auth.yaml", // Authentication APIs
"admin", "admin.yaml" // Admin APIs
);
}
}
Using Named Specs in Scenarios
Default Operations
Operations from the default spec can be called directly:
scenario: List all pets
when I request pets
call ^listPets
then I get a response
assert status 200
Named Spec Operations
To call operations from a named spec, use the using keyword:
scenario: Authenticate and list pets
given I have valid credentials
call using auth ^login
body: {"username": "test", "password": "test"}
then authentication returns a token
assert status 200
extract $.token => authToken
when I request pets
call ^listPets
then I get a response
assert status 200
The syntax is: call using <spec-name> ^<operationId>
Example: Separating Auth from Main API
auth.yaml:
openapi: 3.0.3
info:
title: Authentication API
version: 1.0.0
paths:
/auth/login:
post:
operationId: login
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
username:
type: string
password:
type: string
responses:
'200':
description: Login successful
content:
application/json:
schema:
type: object
properties:
token:
type: string
/auth/logout:
post:
operationId: logout
responses:
'200':
description: Logout successful
petstore.yaml:
openapi: 3.0.3
info:
title: Pet Store API
version: 1.0.0
paths:
/pets:
get:
operationId: listPets
responses:
'200':
description: List of pets
auth.scenario:
# Authentication tests using the 'auth' spec
scenario: Successful Login
given I have valid credentials
call using auth ^login
body: {"username": "test", "password": "test"}
then authentication returns a token
assert status 200
extract $.token => authToken
scenario: Logout
when I log out
call using auth ^logout
then logout is successful
assert status 200
Auto-Resolution
When an operationId is unique across all registered specs, BerryCrush can
automatically resolve which spec to use without the using keyword.
Warning
If the same operationId exists in multiple specs, you must use using
to disambiguate. BerryCrush will throw an AmbiguousOperationException if
it cannot determine which spec to use.
Multi-Host API Testing
In microservices environments, different APIs often run on different hosts or ports. BerryCrush supports this through per-spec base URL configuration.
Configuring Per-Spec Base URLs
Override getSpecBaseUrls() in your bindings to provide different base URLs for each spec:
@Component
@Lazy
public class MicroservicesBindings implements BerryCrushBindings {
@Value("${petstore.host:http://localhost:8080}")
private String petstoreHost;
@Value("${auth.host:http://localhost:8081}")
private String authHost;
@Value("${inventory.host:http://localhost:8082}")
private String inventoryHost;
@Override
public String getOpenApiSpec() {
return "petstore.yaml";
}
@Override
public Map<String, String> getAdditionalSpecs() {
return Map.of(
"auth", "auth.yaml",
"inventory", "inventory.yaml"
);
}
@Override
public Map<String, String> getSpecBaseUrls() {
return Map.of(
"default", petstoreHost,
"auth", authHost,
"inventory", inventoryHost
);
}
}
Cross-Service Scenarios
With multi-host configuration, you can test scenarios that span multiple services:
scenario: Cross-service order flow
given I am authenticated
call using auth ^login
body: {"username": "test", "password": "test"}
then I get a token
assert status 200
extract $.token => authToken
when I check inventory
call using inventory ^checkStock
header_Authorization: "Bearer {{authToken}}"
productId: 123
then product is available
assert status 200
assert $.available equals true
when I create an order
call ^createOrder
header_Authorization: "Bearer {{authToken}}"
body: {"productId": 123, "quantity": 1}
then order is created
assert status 201
extract $.orderId => orderId
File-Level Base URL Overrides
You can also override per-spec base URLs using file-level parameters for testing against different environments:
parameters:
baseUrl.auth: "http://staging-auth.example.com"
baseUrl.default: "http://staging-api.example.com"
scenario: Test against staging
given I authenticate
call using auth ^login
body: {"username": "staging-user", "password": "secret"}
then I get access
assert status 200
Best Practices
Organize by domain: Split specs by functional area (auth, admin, public API)
Use unique operationIds: Avoid duplicate operationIds across specs
Document spec dependencies: Note which tests require which specs
Keep default spec focused: Use the default for your main API operations
Use environment variables: Configure hosts via environment variables for flexibility