Consumer contract testing for ODH Dashboard packages. These tests validate that your frontend (consumer) and the Mock BFF (provider substitute) agree on the API contract by checking real HTTP responses against OpenAPI/JSON Schemas.
{
"devDependencies": {
"@odh-dashboard/contract-tests": "*"
},
"scripts": {
"test:contract": "odh-ct-bff-consumer --bff-dir upstream/bff"
}
}npm run test:contractThat's it! The framework handles everything automatically.
Run contract tests with one command:
# From any package directory (preferred via workspace)
npm run test:contract
# With HTML reports (opens browser automatically)
npm run test:contract -- --open
# Combine options
npm run test:contract -- --open
For debugging or custom workflows, you can run components separately:
# Terminal 1: Start the Mock BFF manually
cd packages/your-package/upstream/bff
go run ./cmd --mock-k8s-client --mock-mr-client --port 8080 --allowed-origins="*"
# Terminal 2: Run contract tests (auto-detects BFF URL)
cd packages/your-package
npm run test:contract
# Or run with custom BFF URL and report opening
CONTRACT_MOCK_BFF_URL=http://localhost:8080 npm run test:contract -- --openUse Turbo for intelligent test execution across multiple packages:
# Run contract tests for all packages with contract tests
npx turbo run test:contract
# Run for specific package
npx turbo run test:contract --filter=@odh-dashboard/model-registry
# Or use npm run (from root package.json)
npm run test:contract -- --open
# Run with automatic report opening (opens browser for each package)
npx turbo run test:contract -- --open
# Combine with package filtering
npx turbo run test:contract --filter=@odh-dashboard/model-registry -- --open
- ✅ Zero configuration - Works out of the box
- ✅ Jest preset - Consistent test configuration across all packages
- ✅ Test utilities - API client, schema validation, health checks
- ✅ Schema validation - OpenAPI/JSON Schema validation for API contracts
- ✅ Schema conversion - Convert OpenAPI/Swagger to JSON Schema
- ✅ Flexible reporting - Use
--opento automatically open HTML reports
Your package should have this structure:
your-package/
├── contract-tests/ # Contract tests directory
│ ├── __tests__/
│ │ └── *.test.ts # Your test files
│ └── jest.config.js # Jest configuration (auto-generated)
└── upstream/
└── bff/ # Mock BFF backend
├── Makefile # Build and run targets
├── go.mod # Go module
└── cmd/ # BFF server code (main.go, etc.)
What's automatically provided:
- ✅ Jest types for testing (
describe,it,expect) - ✅ Contract-tests types for matchers (
toMatchContract) - ✅ Standard ODH TypeScript configuration
The contract test runner automatically manages your Mock BFF lifecycle:
- Runs your BFF directly using
go run ./cmd(no build step) - Starts your BFF server in mock mode with
--mock-k8s-client --mock-mr-client --port 8108 - Waits for BFF to be healthy (checks health endpoint)
- Runs your contract tests using the shared Jest harness
- Cleans up BFF process when tests complete
- Runs your BFF using
go run ./cmd(development/production parity) - Starts your BFF server in mock mode with
--mock-k8s-client --mock-mr-client --port 8108 - Waits for BFF to be healthy (checks health endpoint)
- Runs your contract tests using the shared Jest harness
- Cleans up BFF process when tests complete
Your BFF must have a cmd/ directory with a main.go file that accepts these flags:
flag.BoolVar(&cfg.MockK8Client, "mock-k8s-client", false, "Use mock Kubernetes client")
flag.BoolVar(&cfg.MockMRClient, "mock-mr-client", false, "Use mock Model Registry client")
flag.IntVar(&cfg.Port, "port", 8108, "API server port")Note: The BFF must expose a /healthcheck endpoint for the runner to detect when it's ready.
Create test files in contract-tests/__tests__/:
/**
* @jest-environment node
*/
import { ContractApiClient, loadOpenAPISchema } from '@odh-dashboard/contract-tests';
describe('API Contract Tests', () => {
const baseUrl = process.env.CONTRACT_MOCK_BFF_URL || 'http://localhost:8108';
const apiClient = new ContractApiClient({
baseUrl,
defaultHeaders: {
'kubeflow-userid': 'dev-user@example.com',
'kubeflow-groups': 'system:masters',
},
});
// Load your OpenAPI schema (update path to match your API spec)
const apiSchema = loadOpenAPISchema('upstream/api/openapi/your-api-spec.yaml');
it('should return successful response', async () => {
const result = await apiClient.get('/api/v1/your-endpoint');
expect(result).toMatchContract(apiSchema, {
ref: '#/components/responses/SuccessResponse/content/application/json/schema',
status: 200,
});
});
it('should handle error cases', async () => {
const result = await apiClient.get('/api/v1/your-endpoint?invalid=param');
expect(result).toMatchContract(apiSchema, {
ref: '#/components/responses/ErrorResponse/content/application/json/schema',
status: 400,
});
});
});/**
* @jest-environment node
*/
import { ContractApiClient, loadOpenAPISchema } from '@odh-dashboard/contract-tests';
describe('Your API Endpoint', () => {
const baseUrl = process.env.CONTRACT_MOCK_BFF_URL || 'http://localhost:8108';
const apiClient = new ContractApiClient({
baseUrl,
defaultHeaders: {
'kubeflow-userid': 'dev-user@example.com',
'kubeflow-groups': 'system:masters',
},
});
// Load OpenAPI schema from your checked-in API specs
const apiSchema = loadOpenAPISchema('upstream/api/openapi/your-api-spec.yaml');
it('validates successful response', async () => {
const result = await apiClient.get('/api/v1/your-endpoint');
expect(result).toMatchContract(apiSchema, {
ref: '#/components/responses/SuccessResponse/content/application/json/schema',
status: 200,
});
});
});import { ContractSchemaValidator } from '@odh-dashboard/contract-tests';
// For simple schemas or when OpenAPI is not available
const schemaValidator = new ContractSchemaValidator();
// Define schema manually
const apiResponseSchema = {
type: 'object',
properties: {
data: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
status: { type: 'string' }
},
required: ['id', 'name']
}
},
total: { type: 'number' }
}
};
schemaValidator.loadSchema('ListResponse', apiResponseSchema);
it('validates with manual schema', async () => {
const result = await apiClient.get('/api/v1/your-endpoint');
const validation = schemaValidator.validateResponse(result.data, 'ListResponse');
expect(validation.valid).toBe(true);
});Contract tests require a Mock BFF that can be built and run. Your BFF should have a Makefile with standard targets:
# Key targets your BFF should support:
build: # Builds the BFF binary
run: # Runs the BFF server
clean: # Cleans build artifacts
test: # Runs unit tests
# Example usage:
make build # Creates the BFF binary
make run # Starts the BFF serverYour BFF must support mock flags for testing:
--mock-k8s-client: Use mock Kubernetes client--mock-mr-client: Use mock Model Registry client--port: Specify server port--allowed-origins: CORS configuration
For complex scenarios or when working with OpenAPI schemas that need conversion:
import {
ContractSchemaValidator,
createTestSchema,
extractSchemaFromOpenApiResponse,
} from '@odh-dashboard/contract-tests';
describe('Advanced Schema Validation', () => {
let schemaValidator: ContractSchemaValidator;
beforeAll(() => {
schemaValidator = new ContractSchemaValidator();
// Example: Convert OpenAPI response structure
const openApiResponse = {
'200': {
description: 'API list response',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
data: {
type: 'array',
items: { type: 'string' }
},
total: { type: 'number' }
}
}
}
}
}
};
// Convert to testable schema
const testSchema = createTestSchema(openApiResponse, 'ListResponse');
if (testSchema) {
schemaValidator.loadSchema(testSchema.name, testSchema.schema);
}
});
it('validates converted schema', () => {
const mockResponse = { data: ['item1', 'item2'], total: 2 };
const validation = schemaValidator.validateResponse(mockResponse, 'ListResponse');
expect(validation.valid).toBe(true);
});
});loadOpenAPISchema(path)- Load OpenAPI spec from file (recommended)createTestSchema(openApiResponse, name)- Convert OpenAPI to JSON SchemaextractSchemaFromOpenApiResponse(response)- Extract schema from OpenAPI responseContractSchemaValidator- Manual validation for complex cases
Everything is handled automatically:
- ✅ Jest configuration and types
- ✅ Contract-tests matcher types
- ✅ BFF lifecycle management
- ✅ Schema validation setup
- ✅ Test result reporting
- ✅ TypeScript type definitions
- Jest preset - Uses shared configuration for consistent testing
- Test execution - Runs tests with optimized Jest configuration
- Schema validation - Validates API responses against your schemas
- Reports - Generates standard Jest reports and coverage
Contract testing validates API responses against schemas. The framework supports multiple approaches:
import { ContractApiClient, loadOpenAPISchema } from '@odh-dashboard/contract-tests';
// Load your checked-in OpenAPI specification
const apiSchema = loadOpenAPISchema('upstream/api/openapi/your-api-spec.yaml');
// Validate against OpenAPI schema reference
expect(result).toMatchContract(apiSchema, {
ref: '#/components/responses/SuccessResponse/content/application/json/schema',
status: 200,
});// For simple cases or when OpenAPI isn't available
const schema = {
type: 'object',
properties: {
data: {
type: 'array',
items: { type: 'string' }
}
}
};
expect(result).toMatchContract(schema, { status: 200 });import { ContractSchemaValidator } from '@odh-dashboard/contract-tests';
const schemaValidator = new ContractSchemaValidator();
// Load custom schemas for complex validation scenarios
schemaValidator.loadSchema('CustomResponse', {
type: 'object',
properties: {
success: { type: 'boolean' },
data: { type: 'object' }
},
required: ['success']
});
// Use in tests
const result = await apiClient.get('/api/v1/custom-endpoint');
const validation = schemaValidator.validateResponse(result.data, 'CustomResponse');
expect(validation.valid).toBe(true);If something goes wrong:
- Check directories: Ensure you have
contract-tests/andupstream/bff/directories - Verify BFF setup: Check that your BFF has a Makefile with
buildandruntargets - Test BFF manually: Try
make build && make runin your BFF directory - Check contract tests: Run
npm run test:contractfrom your package directory - Verify OpenAPI specs: Ensure your OpenAPI files are in the expected location
- Check environment: Make sure
odh-ct-bff-consumercommand is available
See the packages/*/contract-tests/ directories for working examples of how different packages implement contract testing.
ContractApiClient- HTTP client for API testing with built-in loggingContractSchemaValidator- JSON Schema validation with AJVOpenApiValidator- OpenAPI/Swagger specification validation
verifyBffHealth- Check BFF health endpointwaitForBffHealth- Wait for BFF to be readyrunContractTests- Run contract tests programmatically
createTestSchema- Convert OpenAPI responses to testable schemasextractSchemaFromOpenApiResponse- Extract JSON schemas from OpenAPI specsconvertOpenApiToJsonSchema- Convert OpenAPI schemas to JSON Schema formatloadOpenAPISchema- Load OpenAPI specs from filescreateSchemaMatcher- Create schema matchers for validation
logTestSetup- Log test setup informationlogApiCall- Log API call detailslogApiResponse- Log API response detailslogApiError- Log API error details