On this page10 sections

Backward Compatibility Testing

Classify eight proposed User Profile API changes as breaking or non-breaking, identify affected consumers, assign risk ratings, generate regression test ideas, and recommend a versioning and release strategy.

Role

API Tester

Difficulty

Intermediate

Time limit

90 min

Category

microservices contract

Any text editor or spreadsheet for change-classification analysis and risk registerOpenAPI diff tool (openapi-diff, oasdiff, or Spectral) — optional but helpful for systematic comparison

Scenario

The User Profile service team is planning a v2.0 release containing eight proposed changes. The QA team has been asked to assess each change for backward compatibility before the release is approved. Three consumer services are currently deployed in production and depend on the current API: the Orders service (calls GET /users/{userId}, reads id, username, and role), the Notifications service (calls GET /users/{userId}, reads id, email, and phoneNumber), and the ShopFlow frontend (calls GET /users/{userId} and POST /users, reads all response fields). Your task is to classify each change, identify which consumers it affects, rate its risk, design regression test ideas, and write a release recommendation.

Requirements

  • 1.Classify each of the eight proposed changes as breaking or non-breaking — with a one-sentence justification for each classification referencing which specific consumer behaviour would be disrupted
  • 2.For each breaking change, identify which of the three consumers is affected and describe the exact failure mode (e.g. Orders service receives an unexpected field name and its role-eligibility check fails)
  • 3.Assign a risk rating (High / Medium / Low) to each change based on the combination of breaking classification and consumer exposure, and justify the rating
  • 4.For each breaking change, propose a backward-compatible migration path (e.g. support both old and new field names during a transition window, API versioning, deprecation notice period)
  • 5.Generate at least two regression test ideas per breaking change — expressed as concrete test cases with endpoint, input, expected behaviour, and the consumer-side assertion that would catch a regression
  • 6.Write a release recommendation (one paragraph per change category): which changes can ship in v2.0 as-is, which require consumer migrations first, and which should be redesigned or deferred

Starter data

  • Proposed changes for v2.0: C1: Add optional phoneNumber field (string | null) to GET /users/{userId} response C2: Rename 'role' field to 'userRole' in GET /users/{userId} response C3: Change 'id' field type from integer to string (UUID format) in GET /users/{userId} response and all related endpoints C4: Add new endpoint GET /users/{userId}/activity — returns recent account events C5: Make 'preferences' a required field in POST /users request body (currently optional) C6: Tighten username validation: minimum length increased from 0 to 3 characters (POST /users) C7: Add new enum value 'guest' to the role field (GET /users/{userId} response) C8: Remove deprecated endpoint POST /users/bulk (marked deprecated 6 months ago)
  • Current consumers: (1) Orders service — calls GET /users/{userId}, reads: id (logged as integer), username, role ('customer'/'admin' conditional logic); (2) Notifications service — calls GET /users/{userId}, reads: id, email, phoneNumber; (3) ShopFlow frontend — calls GET /users/{userId} and POST /users, reads all response fields, stores id in localStorage as a number type
  • Backward-compatibility definition for this exercise: a change is breaking if any currently deployed consumer would need to modify its code to avoid a runtime error, incorrect behaviour, or failed request as a result of the change
  • Notifications service note: it was designed to handle a missing phoneNumber field gracefully (it checks if phoneNumber is present before sending an SMS) — but it currently fails with a null pointer exception if phoneNumber is present but null
  • Deprecation policy: POST /users/bulk has been marked deprecated with a warning header for 6 months; the last confirmed usage was 3 months ago according to access logs

Expected deliverables

  • Change classification table: eight rows, each with the change ID, description, breaking/non-breaking verdict, one-sentence justification, affected consumers (if breaking), and risk rating (High/Medium/Low)
  • Failure mode descriptions: for each breaking change, a concrete description of what breaks at runtime in the affected consumer (e.g. 'Orders service role-eligibility check reads response.role, which is now undefined, causing all users to fail the eligibility gate')
  • Migration path for each breaking change: a step-by-step approach (e.g. parallel field support, API versioning, sunset header strategy)
  • Regression test catalogue: at least two test cases per breaking change, each with endpoint, input, expected status/body, and the consumer-side assertion that catches the regression
  • Release recommendation paragraph: group the eight changes into 'ship now', 'ship after consumer migration', and 'redesign or defer' — with reasoning

Evaluation rubric

DimensionWhat reviewers look for
Breaking vs non-breaking classification accuracyAre the classifications correct and justified by consumer impact rather than intuition? Adding an optional field (C1) is non-breaking for all consumers that ignore unknown fields. Renaming a required field (C2) is breaking for any consumer that reads it by name. Changing a field's type (C3) is breaking even if the new values are semantically equivalent — a consumer that stores id as a JavaScript number will silently corrupt a UUID string. Tightening validation (C6) is breaking for consumers that currently send usernames shorter than 3 characters. Adding a new enum value (C7) is a nuanced case: it is non-breaking for consumers that handle unknown role values gracefully, but breaking for consumers with exhaustive switch/case logic — the candidate should acknowledge this nuance.
Consumer-impact specificityDoes the failure mode description name the specific consumer, the specific line of logic that breaks, and the specific runtime consequence? 'Orders will break' is too vague. 'Orders service reads response.role in its eligibility gate — after the rename to userRole, response.role is undefined; the condition if (response.role === "customer") evaluates to false for all users, causing all order attempts to be rejected' is specific and actionable.
Risk rating calibrationIs the risk rating derived from the combination of breaking classification AND consumer exposure? C3 (id type change from integer to UUID) should be High risk: it affects all three consumers, the ShopFlow frontend silently corrupts the stored ID (localStorage stores it as a number, truncating a UUID), and the Orders service audit log produces malformed entries. C7 (new enum value) should be rated Medium with a note on the exhaustive-switch risk. A flat 'all breaking = High' rating without considering blast radius shows shallow risk thinking.
Migration path viabilityAre migration paths technically concrete and achievable? For C2 (role rename): a viable path is 'return both role and userRole in the response for a two-sprint migration window, notify consumers, verify each consumer has deployed their update via Pact verification, then remove the old field'. Saying 'inform consumers and update their code' is not a migration path — it is a vague instruction that leaves the provider team no implementation detail.
Regression test qualityAre the regression test cases written from the consumer's perspective — verifying the consumer's existing behaviour still works after the change? For C2, a good regression test is: 'Orders service integration test: GET /users/42 → assert response.role === "customer" (using the old field name) — this test should FAIL after the rename, confirming the breaking change'. A test that only checks the provider's response structure without asserting the consumer-side assertion is less valuable.
Release recommendation coherenceDoes the recommendation group changes correctly and provide actionable sequencing? C1 and C4 (additive changes) can ship in v2.0 with no consumer coordination. C2, C3, and C6 require consumer migrations; the recommendation should propose a migration window and specify what must be verified before the provider deploys. C8 (removing a deprecated endpoint) is a judgement call — the recommendation should address whether 3 months of zero traffic is sufficient evidence to remove it safely.

Sample solution outline

  • C1 (add optional phoneNumber): NON-BREAKING — consumers that read response fields explicitly are unaffected by a new optional field; consumers that use strict schema validation may need to update their schemas, but this is a minor update not a runtime break. Risk: Low. Note: Notifications service null-pointer issue is a pre-existing bug triggered by any non-string phoneNumber, not caused by this change — it should be fixed regardless.
  • C2 (rename role to userRole): BREAKING — Orders service reads response.role; after rename, response.role is undefined, failing the eligibility gate for all users. Risk: High. Migration: add both role and userRole to the response for a two-sprint window; notify Orders team via Pact failure report; deploy Orders update first; then remove the old role field in v2.1.
  • C3 (id type integer → string UUID): BREAKING — Orders service logs id as an integer (audit log type error on UUID); ShopFlow frontend stores id in localStorage as a number (JSON.parse truncates UUID to NaN). Risk: High. Migration: this is a significant breaking change affecting all three consumers; consider introducing a new idV2 (string) field alongside the existing id (integer) for a migration period, or implement URL versioning (/v2/users/{userId}).
  • C4 (new GET /users/{id}/activity endpoint): NON-BREAKING — adding a new endpoint does not affect any existing consumer. Risk: Low. Ships in v2.0 as-is.
  • C5 (preferences required in POST /users): BREAKING — ShopFlow frontend's user-registration form does not submit preferences; POST /users will start returning 400. Risk: Medium. Migration: add preferences to the frontend registration payload before deploying this change; verify via contract test.
  • C6 (tighten username minimum length to 3): BREAKING — any consumer that allows users to create accounts with 1–2 character usernames will start receiving 422 errors. Risk: Medium. Migration: add client-side validation in the ShopFlow frontend for minLength 3 before deploying; verify existing user data — no existing users should have a username shorter than 3 characters (if they do, a data migration is required first).
  • C7 (add 'guest' enum value): NON-BREAKING for Orders (Orders handles unknown roles by rejecting the order, which is the correct behaviour for a new guest role) and Notifications (ignores role entirely). POTENTIALLY BREAKING for any consumer with exhaustive switch/case on role without a default branch. Risk: Low with caveat — include a note in release notes advising consumers to add a default branch to any role-based switch statements.
  • C8 (remove POST /users/bulk): BREAKING if any consumer still uses it — however, access logs show zero calls for 3 months. Risk: Low. Recommendation: confirm with all three consumer teams that they do not call this endpoint; if confirmed, ship the removal in v2.0. If any team cannot confirm, delay one sprint while they verify.
  • Release recommendation: Ship immediately in v2.0: C1, C4, C7 (with release-note caveat). Ship after consumer migration: C2 (2-sprint window), C3 (requires URL versioning or parallel-field strategy — significant effort), C5 (frontend update required), C6 (frontend validation update + data check). Redesign or defer: C3 if the parallel-field approach is too complex — consider postponing the UUID migration to v3.0 with proper API versioning in place. Ship conditionally: C8 — only after consumer confirmation from all three teams.

Common mistakes

  • Classifying all changes with any modification as breaking — adding optional fields and new endpoints are additive and non-breaking for consumers that handle unknown fields gracefully; a 'when in doubt, it's breaking' approach will block legitimate API evolution
  • Classifying C3 (integer to UUID string) as only 'medium risk' — the ShopFlow frontend's localStorage stores the id as a number type; a UUID string silently becomes NaN in some JavaScript contexts, corrupting session state with no immediate error visible in the response layer
  • Ignoring the exhaustive-switch nuance for C7 — the 'guest' enum value is non-breaking for most consumers but a candidates who mark it unconditionally non-breaking without noting the switch/case edge case show a gap in API design awareness
  • Proposing migration paths that rely on consumers updating before you have verified they actually have — migration paths must include a verification step (Pact verification run, integration test sign-off) before the old field or endpoint is removed
  • Writing regression tests that only test the provider's response structure without expressing the consumer-side assertion — a test that checks 'GET /users/42 returns a 200 with a role field' does not catch the bug where the consumer's code reads .role and receives undefined after the rename; the test must assert the consumer's processing logic, not just the raw HTTP response
  • Omitting C5 (preferences required) from the breaking list because it is a request-body change rather than a response change — breaking changes on the request side (new required fields, tightened validation) are just as impactful as response changes for consumers that send the request
  • Recommending C8 removal without confirming with consumer teams — access logs show zero traffic for 3 months but a dormant batch job or a consumer that was rolled back could resume calls; the removal should be confirmed with all consumers, not decided unilaterally

Submission checklist

  • Change classification table with all eight changes: breaking/non-breaking verdict, one-sentence justification, affected consumers, and risk rating
  • Failure mode description for each breaking change naming the specific consumer, the specific logic that breaks, and the runtime consequence
  • Migration path for each breaking change — concrete and implementable, not vague instructions
  • Regression test catalogue: at least two test cases per breaking change with endpoint, input, expected result, and consumer-side assertion
  • Release recommendation grouping changes into 'ship now', 'ship after migration', and 'redesign or defer' with reasoning for each group
  • Nuanced handling of C7 (new enum value) acknowledging the exhaustive-switch edge case
  • No changes mis-classified as non-breaking that would cause a runtime failure in a consumer that reads the changed field

Extension ideas

  • +Run oasdiff (https://github.com/Tufin/oasdiff) or a similar OpenAPI diff tool against the current and proposed v2.0 specs and compare its automated breaking-change report to your manual classification — note any discrepancies and explain why the tool agreed or disagreed
  • +Write a consumer-side integration test for the Orders service that verifies the role-eligibility logic and confirm it fails after simulating the C2 rename, then passes after the consumer-side fix
  • +Design an API versioning strategy (URL-based /v1/ vs /v2/ or Accept-Version header) that would allow C3 (integer to UUID) to be shipped without breaking any current consumer, and document the migration timeline