OCPI Testing Tools and Strategies: A Practical Guide

How to test an OCPI implementation properly: mocks, schema validation, partner-style fixtures, and the operational tests that catch real bugs.

OCPI moves money. A bug in your implementation does not show up as a stack trace; it shows up as a miscounted CDR, a session that never closed, or an invoice a partner refuses to pay. Testing an OCPI stack has more in common with testing a payments integration than with testing an ordinary REST API. (If you are new to the protocol, start with what OCPI is before working through testing strategy.)

There is no single official certification suite for OCPI, so no third party will hand you a conformance stamp at the end. You assemble the harness yourself, and the quality of that harness is the only thing standing between a spec misreading and a billing dispute.

This article covers the layers that harness needs, and what each layer actually catches.

The testing pyramid for OCPI

A reasonable structure:

  1. Unit tests: business logic, data transformations, parsers. Fast and exhaustive, run on every commit.
  2. Schema validation: every outgoing payload checked against the OCPI JSON Schema. Run in CI.
  3. Mock-partner integration tests: a mock partner you control, exchanging full request-response cycles for each module. Run in CI.
  4. Real-partner integration tests: against a sandbox or test environment belonging to a real partner. Run weekly, or before each release.
  5. Production smoke tests: after deploying, exercise the live endpoints against a known partner. Run automatically post-deploy.
  6. Observability: monitoring of production traffic to catch issues in flight.

Each layer catches a different class of bug. Skipping a layer is a bet that the bugs it would have caught are not present in your code.

Schema validation: the cheapest big win

The single most valuable testing investment for OCPI is JSON Schema validation in CI.

The specification defines its objects in prose and tables rather than as a normative JSON Schema, so you either generate schemas from those object definitions or adopt a community-maintained set. Either way, pin the schema to a specific version and keep it there. OCPI 2.2 (March 2020) and OCPI 2.2.1 (June 2020) are distinct releases, and a schema built for one will happily pass payloads that a partner on the other rejects.

What to do:

  • Add a CI step that runs every outgoing payload through the validator.
  • For every module you implement (Locations, Sessions, CDRs and so on), keep a representative test that constructs the payload and validates it.
  • Treat schema failures as build failures.

What this catches:

  • Missing required fields.
  • Wrong types, such as a string where a number is expected.
  • Wrong format: date strings that are not ISO 8601 UTC, country codes that are not ISO 3166 alpha-2.
  • Wrong enum values.
  • Out-of-range numerics.

That list is where the “the partner’s parser rejected it” bugs live. Validation turns those into a failed build instead of a support thread, and it does it before the payload leaves your network. Note the boundary of what it buys you: a schema-valid payload can still be semantically wrong, and it will still come back as an OCPI error envelope, so pair validation with a proper reading of OCPI error responses (coming soon).

Mock partners: building your own

A mock partner is a server you build that speaks OCPI well enough to round-trip every message your real code uses. Many of the integration pitfalls teams hit in production are cheap to reproduce and pin down inside a mock first.

A useful mock supports:

  • The Credentials handshake (token A → token B → token C exchange).
  • Versions endpoint negotiation.
  • All modules you support, on both client and server sides.
  • Realistic data: Locations with proper structure, Sessions with valid state transitions, CDRs with consistent meter readings.
  • Optional misbehavior modes (return 500, return malformed JSON, delay the response by 30 seconds) so you can exercise your error handling.

How big a job this is depends on scope rather than on OCPI itself. A mock that only answers Locations GETs is a script. One that runs the full handshake plus both directions of Sessions and CDRs is a small service with its own storage and its own maintenance burden, so decide which one you are signing up for before you start.

Search GitHub for “ocpi mock” or “ocpi simulator” before writing anything, and judge whatever you find on one question: does it cover the modules and the direction, client or server, that you need? A mock that cannot play the side of the conversation you need to test teaches you nothing. If bending someone else’s mock to your interfaces costs more than writing the parts you need, write your own. A hosted OCPI sandbox is a reasonable way to sanity-check message shapes before you commit engineering time either way.

Run the mock as part of your CI suite. Tests should include:

  • Round-trip every module: PUT a Location to the mock, then have the mock GET Locations from you to verify. Repeat for each module.
  • Full session lifecycle: Authorize → Session start → multiple Session updates → Session end → CDR delivery.
  • Error path: send a malformed payload and verify your error response is itself well-formed.
  • Pagination: ask for paginated data and verify you handle the Link header correctly.

Real-partner sandbox testing

Mocks catch your bugs. Real partners catch the differences between your reading of the spec and theirs, which is a category your mock cannot reach because you wrote the mock from your own reading.

Ask during onboarding whether a partner runs a sandbox and what data lives in it. Sandbox availability is a contract-discussion question, not a safe assumption, and it is worth raising before you have committed to a launch date. Where one exists:

  • Exchange data in the sandbox before going live with that partner.
  • Run regression tests against it on a regular cadence, monthly or quarterly.
  • Smoke-test there before a major release.

What you find in a sandbox that the mock never showed you:

  • Subtle differences in how the partner formats data, including extra fields, absent optional fields and formatting variations.
  • Their tariff structures, which will not look like your fixtures.
  • Their Locations structure, which will not look like your idealised test Locations.
  • Real latency. Partners are slower than an in-memory mock.
  • Real error rates. Networks have hiccups that your mock never simulates.

If you are wiring up a first connection, the OCPI connection test guide walks the handshake and first calls step by step.

Test data realism

A common testing failure: the test data is too clean. Production data carries oddities your fixtures should also carry.

  • Locations with mixed-case names, special characters and multi-language strings.
  • Tariffs with awkward structures, including unusual price magnitudes and layered restrictions.
  • Tokens with unusual character sets.
  • Session timestamps that span a timezone change, including DST transitions.
  • CDRs with negative energy values, from V2G or from measurement error.
  • Empty optional fields.
  • Maximum-length strings.

Generate realistic test data rather than convenient test data. Borrow from real partner samples where you have permission, anonymised. Test the unhappy data shapes, not only the ones your own code would produce.

The Credentials handshake deserves extra attention

Credentials is a small module with a large number of failure states, which is why it earns disproportionate test coverage. The token exchange also carries your authentication and security posture, so getting these paths right protects more than registration alone. The mechanics of the three-token flow are covered in Credentials and Versions.

Test cases to include:

  • Happy path: token A exchange, register, both sides accept.
  • Token A already used: should reject.
  • Token A expired: should reject.
  • Token B unreachable when the partner tries to register: handle the timeout gracefully.
  • Multiple registrations from the same partner: handle as token rotation, or as a duplicate.
  • Token rotation in flight when the partner reboots: recover cleanly.
  • Mismatched country_code or party_id: reject with a clear error.
  • A Credentials object carrying a role you do not recognise: handle gracefully by accepting the role, logging it and continuing.
  • Multi-role Credentials: a partner declaring CPO and Hub roles in one object.

Pass all of those and you have a robust handshake. Fail one and you have a production support ticket waiting for a date.

Idempotency testing

OCPI partners retry. Your code has to handle retries correctly.

Test for each module:

  • POST the same Session update twice. Verify the state is correct and not duplicated.
  • POST the same CDR twice. Verify only one CDR is stored, since the CDR is the billing record and a duplicate is a real invoice.
  • PUT a Location twice with identical content. Verify no spurious update event fires.
  • PUT a Location twice with slightly different content. Verify the update is applied.

The last two together are the actual requirement: idempotent on identical content, applied on different content. Testing only one of them is how duplicate-handling bugs survive.

Concurrency testing

In production you receive many requests in parallel, and your code has to handle concurrent updates without corrupting state.

Test:

  • Concurrent Session updates for the same session. Verify the final state is consistent.
  • Concurrent Location PUTs for the same location. Verify last-write-wins, or whatever your defined semantics are.
  • Concurrent Authorize requests with the same idTag. Verify your eMSP query does not fan out into N duplicate lookups.

Load generation needs sustained parallel connections and stable timing, and it is slow. Run it with targeted tools such as k6, Locust or your own scripts, on its own schedule, separate from the per-commit suite.

Tools and libraries

JSON Schemas. Generated from the spec’s object definitions, or adopted from a community repository and pinned to your version. This is the piece that runs on every commit.

Language SDKs. Helper libraries exist for several languages and absorb some of the boilerplate around pagination, headers and version negotiation. Read the source before you depend on one. Two questions decide it: which OCPI version does it target, and does it implement both client and server roles? A library that only speaks one direction leaves half your surface untested.

A mock partner. Yours, or forked from an open-source starting point, as covered above.

A saved HTTP request collection. Whatever client your team already uses, a stored set of requests is the fastest way to poke at a live endpoint during exploratory work. Build it from your own request bodies so it drifts with your implementation rather than against it. For worked payloads to start from, see OCPI request examples.

Charge-point simulators. These speak OCPP, not OCPI, so they sit on the far side of your CSMS. They matter because they generate the session and meter-value traffic that eventually becomes an OCPI CDR, which lets you test the whole path instead of only the roaming hop. Check which OCPP version a given simulator targets before wiring it in.

The specification’s example payloads. The examples in the OCPI documents are already-correct fixtures written by the people who wrote the spec. Use them.

CI integration

How the layers land in a pipeline:

  1. Unit tests run on every commit. Fast and exhaustive, over business logic.
  2. Schema validation runs on every commit. Verifies data structures conform to the spec.
  3. Mock-partner integration tests run on every commit. Full round-trip for each module.
  4. Real-partner sandbox tests run nightly. Catches regressions caused by interpretation drift on either side.
  5. Production smoke tests run after every deploy. Verifies the deploy works before you declare it done.

Keep the per-commit set to a few minutes and push everything slower into the nightly run. This is not an aesthetic preference. A suite that takes half an hour gets skipped, and a skipped suite catches nothing.

Observability is testing too

Your production system is the longest-running and most comprehensive test of your OCPI implementation. Observability is what turns it into a signal you can read:

  • Per-partner error rates. A spike confined to one partner points at that integration rather than at your core.
  • Per-module latency. A slow module hints at a database or logic problem underneath it.
  • Per-message-type counts. A drop in an expected message type means a broken pipe somewhere upstream.
  • CDR-to-session ratio. Every session should produce a CDR, so a divergence is a billing bug with a number attached.

Build dashboards for these and alert on the anomalies. Production data finds the bugs your tests did not think to look for.

What good test coverage looks like

A target to aim at:

  • Unit test coverage: 70-90% of business logic.
  • Module integration tests: every module you support, on both client and server sides.
  • Edge case tests: 5-10 per module, covering unhappy paths.
  • Credentials handshake: 10 or more cases across the happy path and the edge cases above.
  • Performance tests: load against a realistic mock at two or three times your expected production traffic.
  • Schema validation: every outgoing payload validated in CI.

These are targets you set, not measurements of anyone else’s stack. Writing them down is worth doing anyway, because it turns coverage arguments into a comparison against a number rather than a contest of instincts.

The honest summary

Every OCPI test you write encodes your reading of the specification. So does your partner’s test suite. Neither suite can tell you which reading is correct; each only confirms that its author has not drifted from their own earlier reading. That is why the split between mocks and sandboxes matters more than any coverage percentage. The mock protects you from yourself, and only a live partner can reveal that the two of you disagree about a clause.

Which means the interpretation surprise is not a launch-phase problem you eventually finish. It arrives fresh with every new partner, and the teams that stay calm about it are the ones who budgeted for it permanently instead of treating each occurrence as an incident.

Quick check

Q1. What does a mock partner catch that a real-partner sandbox generally does not?
Q2. Why does the CDR-to-session ratio make a useful production observability signal?
Q3. Which idempotency behavior should a Location PUT test verify?
Q4. Where should concurrency stress tests like k6 or Locust run?
Q5. What is the state of official OCPI test suites?

Frequently asked questions

Are there official OCPI test suites?

OCPI has no single official certification suite, so nobody issues you a conformance stamp. You assemble the harness yourself: JSON Schemas pinned to the version you implement, a mock partner you control, and sandbox access negotiated with each partner during onboarding. The specification documents also contain worked example payloads, which make good fixtures.

Should I test against real partners or only mocks?

Both, because they catch different things. A mock is a server you control, so it runs in CI on every commit and catches your own bugs cheaply. A partner sandbox surfaces the places where their reading of the spec differs from yours, which no mock can predict because you wrote the mock from your own reading. Weight the bulk of automated coverage toward mocks, and reserve sandbox runs for pre-release and periodic regression checks.

What is the most underrated test type for OCPI?

Schema validation in CI. Run every outgoing payload through a JSON Schema validator before it leaves your network, and treat a validation failure as a build failure. It catches missing required fields, wrong types, non-ISO timestamps and bad enum values, which are exactly the bugs that otherwise surface as a partner support ticket days later.

How do I test the Credentials handshake?

Build a mock partner that implements the three-token flow, then run the full handshake inside your test suite. Cover the unhappy paths as well: token A already used, token A expired, token B unreachable, a response lost mid-rotation, a mismatched country_code or party_id. The handshake is a small module with a large number of failure states, so coverage there is cheap relative to what it prevents.

Found this useful? Share it.