QA Interview
Contract Testing Take Home Assignment (2026)
Complete a contract testing take home assignment with runnable TypeScript tests, CI checks, 50+ interview answers, review criteria, demo tips, and trade-offs.
19 min read | 3,760 words
TL;DR
A strong submission turns one API compatibility risk into a runnable, reviewable repository. Define the minimum contract, exercise the real consumer parser, verify a running provider, demonstrate a breaking change, and make the same checks pass in CI.
Key Takeaways
- Translate an ambiguous prompt into a small compatibility risk and document every assumption.
- Keep the shared contract consumer-focused so harmless provider fields do not create brittle failures.
- Prove the contract from both directions with a real consumer client and a running provider implementation.
- Add breaking-change, error, and evolution cases instead of submitting one decorative happy-path test.
- Run type checking and contract tests in CI with deterministic fixtures and useful failure output.
- Present scope, trade-offs, limitations, and the next production step as clearly as the code itself.
A contract testing take home assignment asks you to prove that a consumer and provider can evolve without silently breaking each other. The strongest submission is not the largest framework. It is a small, runnable project that makes one compatibility promise explicit, verifies both sides, and explains what the test does not prove.
This guide gives you a complete TypeScript approach plus 50+ questions an interviewer may ask during review. Build the example, replace its order domain with the prompt's domain, and rehearse the reasoning in QAJobFit interview practice. For deeper theory, keep the complete contract testing guide open while you work.
TL;DR
| Assignment area | Evidence to submit | Reviewer signal |
|---|---|---|
| Scope | One consumer, one provider, one risky interaction | You can prioritize |
| Contract | Minimal executable response shape | You understand compatibility |
| Consumer proof | Real parsing code tested against a fixture server | The contract reflects usage |
| Provider proof | Running HTTP handler checked against the same schema | The provider actually conforms |
| Evolution | One additive case and one detected breaking case | You can reason about change |
| Delivery | Type check, test command, and CI workflow | Another engineer can trust it |
| Presentation | Assumptions, limits, and a five-minute demo | You communicate engineering judgment |
1. Decode the contract testing take home assignment
Q: What is the assignment really evaluating?
It evaluates whether you can turn an interface risk into fast, trustworthy evidence. Reviewers look for scope control, executable tests, failure diagnosis, and a defensible release decision. Tool fluency matters less than showing why a particular mismatch would be caught before deployment.
Q: What should you clarify before writing code?
Clarify the protocol, source of truth, consumer behavior, provider ownership, supported runtime, and expected submission time. Ask whether the reviewer expects consumer-driven Pact tests, specification conformance, or freedom to choose. If answers are unavailable, record a narrow assumption and make the design easy to replace.
Q: How do you choose the first interaction?
Choose a read or command whose response controls a real consumer branch. An order lookup works because status, money, customer identity, and line items all have compatibility meaning. Avoid selecting a health endpoint, since a 200 response reveals almost nothing about consumer expectations.
Q: How large should the solution be?
Fit the essential path into a repository a reviewer can understand in about fifteen minutes. One well-tested boundary with happy, error, and evolution cases is stronger than twelve shallow endpoints. Timebox optional polish until the core test fails for the right reason and passes for the right reason.
Q: What assumptions belong in the README?
State that JSON is the transport, the consumer tolerates additional response fields, money is integer cents, and order status is a closed set for this exercise. Name the Node.js version, install command, and whether the provider is a sample implementation or production code. These statements turn hidden design choices into reviewable decisions.
2. Choose a contract-testing strategy
Q: When is a schema-first approach appropriate for this exercise?
Use schema-first testing when the assignment asks for an executable API boundary but does not require a broker workflow. A shared runtime schema can validate fixtures, consumer parsing, and provider responses with little infrastructure. It demonstrates core compatibility reasoning while leaving publication and deployment matrices as an explicit next step.
Q: When should you choose consumer-driven Pact instead?
Choose Pact when the prompt names it, multiple independently released consumers matter, or provider verification results must feed deployment decisions. The consumer then publishes concrete interactions and the provider verifies them with controlled states. The API contract testing with Pact tutorial covers that broker-backed workflow.
Q: How is this different from an integration test?
The contract check focuses on the observable request and response that the consumer relies upon. An integration test also proves real wiring such as authentication configuration, database access, DNS, and network policy. Keep both labels honest so a passing boundary check is not sold as proof of the entire environment.
Q: What role does a mock server play?
A fixture server gives the real client predictable HTTP behavior without needing the provider process. It proves that the consumer sends the expected route and can parse a compliant response. It cannot prove provider conformance, so the submission also starts the actual sample provider and validates its output separately.
Q: Why not validate the complete OpenAPI document only?
An OpenAPI check can detect structural drift across many endpoints, but it may not show which fields this consumer truly needs. A valid response can still carry a business value the client cannot handle. Use specification validation as broader coverage, then retain focused executable examples for consumer-observed semantics such as status mapping and integer money.
3. Structure a reviewer-friendly repository
Q: Which files should the take-home contain?
Include the shared contract, actual consumer client, sample provider, consumer test, provider verification test, breaking-change test, CI workflow, and concise README. Keep generated reports out of source control unless the prompt explicitly requests them. A flat, predictable layout lets the reviewer trace the same contract through both sides.
Q: What runtime makes the example easy to review?
Use Node.js 24 LTS, TypeScript, the built-in node:test runner, and Zod 4. This combination keeps the dependency list short while providing HTTP, fetch, static types, and runtime validation. Major-version ranges avoid pretending that one patch release is essential to the technique.
{
"name": "order-contract-assignment",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "^24.0.0",
"tsx": "^4.20.0",
"typescript": "^5.9.0",
"zod": "^4.0.0"
}
}
{
"compilerOptions": {
"target": "ES2023",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noEmit": true,
"types": ["node"],
"lib": ["ES2023", "DOM"]
},
"include": ["src", "test"]
}
Verify the scaffold before adding implementation:
npm install
npm run typecheck
Expected result: dependency installation completes and tsc exits with code 0.
Q: Why use the built-in test runner?
The built-in runner supports asynchronous tests, assertions, filtering, and standard exit codes without another framework abstraction. That keeps attention on the contract rather than configuration. A company-standard runner would also be valid if the README explains the choice and the test remains deterministic.
Q: Should consumer and provider live in separate repositories?
Production services usually do, but a take-home can keep them together to reduce setup. Preserve conceptual separation through dedicated modules and tests even inside one repository. Explain that a real pipeline would version and exchange the contract artifact rather than import source across team boundaries.
Q: What makes the submission reproducible?
Pin the lockfile, specify the runtime, expose one install command, and avoid dependencies on local ports or external services. Start servers on an operating-system-assigned port and close them in finally blocks. Reproducibility means the reviewer can clone, run, and receive the same result without private configuration.
4. Define the minimum consumer contract
Q: Which response properties should the order consumer require?
Require only values its code reads: order ID, current status, total in cents, customer ID, and at least one line item. Validate identifiers and numeric boundaries that would otherwise corrupt client behavior. Let unrelated provider fields pass so an additive response does not become an artificial breaking change.
// src/orderContract.ts
import { z } from 'zod';
export const orderContract = z.object({
id: z.string().regex(/^ord_[a-z0-9]+$/),
status: z.enum(['PLACED', 'SHIPPED']),
totalCents: z.number().int().nonnegative(),
customer: z.object({
id: z.string().min(1),
}),
items: z.array(z.object({
sku: z.string().min(1),
quantity: z.number().int().positive(),
})).min(1),
});
export type Order = z.infer<typeof orderContract>;
Verify the contract compiles:
npm run typecheck
Expected result: TypeScript reports no errors and exits with code 0.
Q: Why is totalCents an integer?
Integer cents avoid floating-point ambiguity and make the wire unit explicit. The lower bound rejects negative totals that this consumer cannot display safely. If refunds are a separate resource, documenting that domain rule is better than weakening the field for hypothetical reuse.
Q: Why allow unknown response properties?
Zod objects accept and strip unrecognized keys by default, which models a tolerant consumer here. This permits the provider to add metadata without forcing a coordinated release. If the real client uses strict decoding, the assignment should expose that limitation and test additions as potentially breaking.
Q: How should optional and nullable fields be modeled?
Optional means a property may be absent, while nullable means it may be present with null. Do not combine them automatically because consumer fallback logic can distinguish the two states. Add each modifier only after demonstrating that the client handles that wire representation.
Q: Should the schema include every business rule?
Keep rules that define safe parsing and immediate consumer behavior in the boundary schema. Cross-record rules, authorization, inventory availability, and long workflows belong in provider component or integration tests. A concise schema is easier to review and less likely to duplicate the provider's internals.
5. Prove the consumer behavior
Q: What should the real consumer client do?
It should send the actual method, encoded path, and relevant headers used by application code. It must reject non-success HTTP status codes before parsing the body. Runtime schema parsing then converts an untrusted JSON payload into the typed Order returned to callers.
// src/orderClient.ts
import { orderContract, type Order } from './orderContract';
export async function getOrder(baseUrl: string, id: string): Promise<Order> {
const response = await fetch(`${baseUrl}/orders/${encodeURIComponent(id)}`, {
headers: { accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`order request failed: ${response.status}`);
}
const payload: unknown = await response.json();
return orderContract.parse(payload);
}
Q: How does the consumer test avoid a fake client?
The test imports getOrder, the same function application code would call. Its fixture server supplies only the remote boundary, then records the actual request for assertion. This catches route, header, status, JSON, and parser regressions in one focused test.
// test/consumer.test.ts
import assert from 'node:assert/strict';
import { createServer } from 'node:http';
import type { AddressInfo } from 'node:net';
import test from 'node:test';
import { getOrder } from '../src/orderClient';
test('consumer requests and parses the minimum order contract', async () => {
let observedAccept = '';
const server = createServer((request, response) => {
observedAccept = String(request.headers.accept ?? '');
assert.equal(request.method, 'GET');
assert.equal(request.url, '/orders/ord_42');
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({
id: 'ord_42',
status: 'PLACED',
totalCents: 2599,
customer: { id: 'cus_7' },
items: [{ sku: 'BOOK-1', quantity: 1 }],
}));
});
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
const { port } = server.address() as AddressInfo;
try {
const order = await getOrder(`http://127.0.0.1:${port}`, 'ord_42');
assert.equal(order.totalCents, 2599);
assert.equal(observedAccept, 'application/json');
} finally {
await new Promise<void>((resolve, reject) =>
server.close((error) => error ? reject(error) : resolve()),
);
}
});
Verify only the consumer side:
node --import tsx --test test/consumer.test.ts
Expected result: one test passes and the process exits with code 0.
Q: Why assert the request as well as the response?
Compatibility is bidirectional at the HTTP exchange even when the response receives most attention. A provider may require the correct path, method, query, media type, or idempotency header. Request assertions reveal client drift before a confusing provider-side 404 or 415 occurs.
Q: What consumer error case should be added next?
Return a 404 problem response and assert that getOrder rejects with order request failed: 404. That test proves status handling independently from successful body parsing. If the application branches on a stable error code, introduce a separate error schema rather than matching human-readable text.
Q: Does this test generate a portable contract artifact?
No, it executes a shared schema and a representative interaction inside one repository. That is sufficient for the scoped exercise but not for independently versioned organizations. A production extension would publish Pact interactions or a versioned schema and record verification against immutable consumer and provider versions.
6. Verify the running provider
Q: What distinguishes provider verification from fixture validation?
Provider verification calls a real handler produced by provider code. The test observes its status, media type, and decoded response before applying the shared contract. Validating a hand-written fixture alone would only prove that the fixture author can satisfy the schema.
// src/provider.ts
import { createServer } from 'node:http';
export function createOrderServer() {
return createServer((request, response) => {
if (request.method !== 'GET' || request.url !== '/orders/ord_42') {
response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ code: 'ORDER_NOT_FOUND' }));
return;
}
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify({
id: 'ord_42',
status: 'SHIPPED',
totalCents: 2599,
customer: { id: 'cus_7' },
items: [{ sku: 'BOOK-1', quantity: 1 }],
updatedAt: '2026-08-06T10:00:00Z',
}));
});
}
// test/provider-contract.test.ts
import assert from 'node:assert/strict';
import type { AddressInfo } from 'node:net';
import test from 'node:test';
import { orderContract } from '../src/orderContract';
import { createOrderServer } from '../src/provider';
test('provider response satisfies the consumer order contract', async () => {
const server = createOrderServer();
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
const { port } = server.address() as AddressInfo;
try {
const response = await fetch(`http://127.0.0.1:${port}/orders/ord_42`);
assert.equal(response.status, 200);
assert.match(response.headers.get('content-type') ?? '', /^application\/json/);
const result = orderContract.safeParse(await response.json());
assert.equal(result.success, true, result.error?.message);
} finally {
await new Promise<void>((resolve, reject) =>
server.close((error) => error ? reject(error) : resolve()),
);
}
});
Verify the provider boundary:
node --import tsx --test test/provider-contract.test.ts
Expected result: one provider contract test passes, including the harmless updatedAt addition.
Q: Why does the extra updatedAt field pass?
The consumer never promised to reject additional properties, so the provider can add one safely. This is deliberate evidence of additive evolution rather than an accidental omission. The test would change only if strict decoding were an actual consumer constraint.
Q: How would provider states work with a database-backed service?
Create a test-only setup function that establishes order ord_42 exists and is shipped before verification. Express the state as a domain fact rather than SQL in the contract, then implement it through the provider's repository or test API. Isolate and clean the record so parallel verification cannot mutate shared data.
Q: Should downstream services be real during provider verification?
Stub dependencies below the provider boundary unless their behavior is part of the contract under review. Otherwise a shipping outage can fail an order response contract and obscure ownership. Add a smaller integration suite for real downstream wiring, credentials, and network behavior.
Q: What if the provider is not available to the candidate?
Submit an adapter or sample implementation and label it clearly. Demonstrate how the verification test would target a configurable base URL in a real pipeline. Never claim that a hand-built double proves an unavailable production service conforms.
7. Test breaking changes and error contracts
Q: How do you demonstrate that the suite catches a breaking type change?
Feed the contract a response that changes totalCents from an integer to a formatted string. Assert that parsing fails, which keeps the negative demonstration green while proving the schema rejects the incompatible payload. The failure path should point directly to totalCents, giving the reviewer actionable evidence.
// test/breaking-change.test.ts
import assert from 'node:assert/strict';
import test from 'node:test';
import { orderContract } from '../src/orderContract';
test('detects a breaking totalCents type change', () => {
const result = orderContract.safeParse({
id: 'ord_42',
status: 'PLACED',
totalCents: '$25.99',
customer: { id: 'cus_7' },
items: [{ sku: 'BOOK-1', quantity: 1 }],
});
assert.equal(result.success, false);
if (!result.success) {
assert.equal(result.error.issues[0]?.path.join('.'), 'totalCents');
}
});
Verify the breaking-change guard:
node --import tsx --test test/breaking-change.test.ts
Expected result: the test passes because the incompatible response is rejected at the expected field.
Q: Is adding an enum value always backward compatible?
No, a consumer may use an exhaustive switch that understands only PLACED and SHIPPED. A new CANCELLED value can reach an impossible branch even though the JSON type remains a string. Either make unknown handling explicit in the consumer or treat the new value as a coordinated change.
Q: Which error responses deserve contracts?
Cover errors that change consumer control flow, such as 400 validation failure, 401 reauthentication, 404 missing order, 409 duplicate submission, and 429 retry. Assert stable status and machine-readable codes, plus only the detail fields the client consumes. Do not freeze prose messages if they are intended for logs or localization.
Q: How do you test an optional field safely?
Write one example where the field is absent and another where it contains a valid value. Add a null case only if the provider can emit null and the consumer deliberately handles it. This matrix prevents vague optionality from hiding a parser crash.
Q: What is a subtle breaking request change?
Making a formerly optional request header mandatory can break deployed consumers without changing the response schema. Narrowing accepted date formats or rejecting extra request properties has the same risk. Contract coverage must therefore include what the consumer sends, not only what the provider returns.
8. Put contract evidence in CI
Q: What should the pull-request pipeline run?
Install from the lockfile, type-check the project, and execute all contract tests on the declared Node.js version. Separate output by step so a compiler error is distinguishable from a compatibility mismatch. Keep the same commands developers run locally to prevent a second, hidden execution path.
# .github/workflows/contract-tests.yml
name: contract-tests
on:
pull_request:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm test
Verify the exact CI sequence locally:
npm ci
npm run typecheck
npm test
Expected result: type checking succeeds and all three contract tests pass.
Q: When should a compatibility failure block merge?
Block once the test is deterministic, the contract represents a supported consumer, and ownership is clear. During initial adoption, report failures without gating while teams remove fixture and metadata noise. A permanent nonblocking check becomes theater, so define the date and conditions for enforcement.
Q: Which artifacts help review a failed run?
Preserve structured validator issues, the consumer and provider versions, relevant request metadata, and the failing response with secrets removed. Link the artifact to the exact commit and workflow run. Avoid uploading access tokens, personal data, or complete production payloads simply to improve debugging.
Q: How would CI change for separate repositories?
Consumer CI would publish an immutable contract artifact tagged with its commit, and provider CI would verify relevant artifacts against its build. A broker webhook or scheduled job would close the feedback loop when either side changes. Before deployment, query compatibility for the exact versions present in the target environment rather than comparing two arbitrary latest builds.
Q: How do you prevent contract test flakiness?
Use operating-system-assigned ports, deterministic clocks and fixtures, isolated provider states, and no live third parties. Match variable values by constraint instead of exact timestamp or generated ID. Treat intermittent verification as a defect because an unreliable gate cannot support a release decision.
9. Diagnose failures and defend trade-offs
Q: The provider test fails after a harmless metadata addition. What do you inspect?
Check whether the consumer schema was made strict or the test snapshots the full body. Trace the rejected field to actual client code before deciding it belongs in the contract. If the client ignores it, relax the expectation and document tolerant reading as the intended compatibility policy.
Q: The consumer test passes but production calls fail. What might be missing?
Authentication, gateway rewriting, TLS, DNS, database state, or downstream availability may be outside the isolated contract. The test could also target an example that omits a production-only error branch. Add focused integration coverage for the missing wiring without inflating the contract suite into an end-to-end environment.
Q: How do you recognize an over-specified contract?
It fails when the provider changes data the consumer never reads, such as display text, timestamps, or field order. Review each assertion against parsing or branching code and remove obligations without a consumer reason. Frequent coordinated edits for harmless additions are a practical warning that the contract has become a snapshot.
Q: How do you recognize an under-specified contract?
It accepts values that cause the real client to misbehave, such as negative totals, empty identifiers, or unknown statuses. Passing only z.string() for every textual field may prove syntax while missing business compatibility. Add constraints from observed consumer decisions, not speculative provider rules.
Q: What security issues can appear in a take-home?
Hard-coded credentials, copied customer payloads, public provider-state endpoints, and verbose CI logs can expose sensitive material. Use synthetic examples, local-only setup controls, secret scanning, and redacted failure artifacts. Mention authorization contract cases without embedding a usable bearer token in the repository.
For broader boundary techniques, compare this solution with validating JSON response schemas, testing API versioning, and testing backend contracts without production.
10. Present the contract testing take home assignment
Q: What should the README say first?
Lead with the protected risk: the order consumer requires stable identifiers, supported statuses, integer cents, customer identity, and line items. Follow with prerequisites and the three commands needed to install, type-check, and test. Put architecture notes after the quick start so the reviewer can verify the result immediately.
Q: How should you run a five-minute demo?
Start with a green test run, then open the contract and trace one field through consumer and provider tests. Temporarily describe the totalCents string change and show the committed negative guard rather than editing files live. Finish with the CI gate, known limitation, and next production increment.
Q: Which limitation should you volunteer?
State that a shared schema in one repository does not provide independent publication, deployed-version matrices, or broker-mediated provider verification. Explain that the exercise optimizes for reviewability under a short timebox. Then outline migration to Pact or versioned OpenAPI without pretending it is already implemented.
Q: How should you explain an unfinished enhancement?
Name the omitted behavior, its risk, and the smallest next test rather than apologizing broadly. For example, say that 404 body parsing is not yet modeled and would receive a stable error schema plus consumer branch test. This gives the reviewer a concrete backlog and shows you can stop intentionally.
Q: What follow-up question should you expect?
Expect the interviewer to ask how the design changes when consumer and provider deploy independently. Answer with immutable contract publication, provider verification, environment recording, and an exact-version deployment query. Use the microservices contract interview guide to practice that extension.
How Interviewers Grade Your Answers
Q: What earns a strong score for technical correctness?
The tests must execute real code, fail on an intentional incompatibility, and avoid invented framework methods. Assertions should reflect HTTP and domain semantics rather than merely checking that a body exists. Consistent imports, deterministic teardown, and a clean type check show the sample was actually designed to run.
Q: What earns a strong score for test strategy?
A high-quality answer distinguishes consumer proof, provider conformance, integration wiring, and end-to-end behavior. It prioritizes one risky boundary and covers a meaningful error or evolution case. The candidate can explain both what is protected and what remains outside the evidence.
Q: What earns a strong score for maintainability?
Reviewers reward minimal dependencies, readable names, one source of contract truth, and failures that identify the mismatched path. They also look for tolerant response handling and deterministic state setup. A clever abstraction that hides the interaction usually scores worse than direct code with an obvious change path.
Q: What earns a strong score for delivery judgment?
The submission must be cloneable, documented, and automated with the same commands in local and CI environments. It should identify when a compatibility result blocks a release and which version pair the result covers. Honest limits and a realistic production extension demonstrate judgment beyond the happy path.
Q: How should you discuss experience you do not have?
Say which contract workflow you implemented and which broker or platform features you only studied. Map familiar schema, API, CI, and integration concepts to the missing tool without inventing production usage. Credible boundaries make the rest of your explanation more trustworthy.
Common Mistakes
Q: What is the most common scope mistake?
Candidates build a generic framework, multiple services, Docker orchestration, and dashboards before one valuable incompatibility is covered. The result consumes review time while hiding the central promise. Complete one boundary end to end, then add only enhancements tied to stated evaluation criteria.
Q: What is the most common assertion mistake?
Copying a full provider response into an exact snapshot makes incidental values contractual. Timestamps, metadata, and unused fields then break the suite even when the consumer is safe. Match the smallest shape and semantics needed by actual client behavior.
Q: What is the most common evidence mistake?
A mock-based consumer test is presented as proof that the provider conforms. Because the mock was configured by the test author, it can agree perfectly while production returns a different payload. Run a separate provider verification and label every piece of evidence accurately.
Q: What is the most common CI mistake?
A workflow runs different commands or runtime versions from the README. That creates failures the candidate cannot reproduce and weakens confidence in the lockfile. Use a single script surface and exercise the clean-install path before submission.
Q: What is the most common presentation mistake?
Candidates narrate every file but never state the release risk their tests reduce. Reviewers then must infer why the solution matters. Lead with the incompatible change, show the detecting test, and end with the decision that evidence enables.
Contract testing take home assignment conclusion
A publishable submission for this exercise is small, executable, and explicit about evidence. Build the consumer-focused schema, test the real client against a fixture boundary, verify the running provider, prove a breaking change is rejected, and run everything through a clean CI path.
Then practice explaining the trade-offs aloud. If you want to compare this schema-first solution with broker-backed consumer-driven testing, study consumer-driven Kafka contract testing, refine your story in QAJobFit practice, and make sure your project evidence is visible in the resume workspace.
Interview Questions and Answers
How would you approach a contract testing take home assignment?
I would identify one consumer-provider incompatibility with real business impact, then document the assumptions. I would implement the minimum consumer contract, prove the real client can use it, and verify the running provider against it. Finally, I would add a breaking-change case, clean CI execution, and a short limitations section.
Why did you choose schema-first testing for this solution?
The exercise is tool-neutral and timeboxed, so a runtime schema keeps both sides executable without broker setup. It lets me demonstrate consumer parsing, provider conformance, and evolution behavior directly. For separately deployed repositories, I would publish versioned artifacts or move to Pact.
How do consumer and provider contract tests differ?
The consumer test proves the client sends an expected request and correctly handles a compliant response. Provider verification proves the actual provider implementation emits behavior accepted by the consumer contract. Both are required before claiming compatibility.
How do you avoid over-specifying an API contract?
I trace each assertion to data the consumer parses, displays, or uses for branching. Variable values receive constraint-based checks, and additional response fields remain allowed unless the real decoder is strict. This prevents harmless provider additions from forcing coordinated releases.
What breaking changes would your tests detect?
They detect removed required fields, incompatible types, invalid identifiers, unsupported status values, negative totals, and empty item collections. Request assertions also expose method, path, and media-type drift. Separate error cases can protect stable status and machine-readable error codes.
Why do contract tests not replace end-to-end tests?
Contract evidence is intentionally limited to an interface between components. It does not prove gateway policy, credentials, DNS, persistence, or a complete user journey. I retain a thin set of integration and end-to-end tests for those risks.
How would this design work across separate repositories?
Consumer CI would publish an immutable contract version, and provider CI would verify selected contracts against its build. Results and environment deployments would be recorded centrally. A predeployment query would then evaluate the exact versions that are about to meet.
How do you keep provider verification deterministic?
I establish named domain states with isolated records, fixed clocks where needed, and no live third-party dependency. Servers use assigned ports and tests close every resource in a finally block. Variable values are checked by constraints rather than exact generated data.
What would you improve with another day?
I would add a stable error contract for 404 and 409 responses, then publish machine-readable validation output from CI. If independent deployment were in scope, I would add broker-backed contract publication and verification metadata. I would not expand endpoint count until those lifecycle concerns were covered.
How would you present this assignment in an interview?
I would begin with the incompatible change the project prevents, run the green suite, and trace one field through consumer and provider evidence. Then I would show the breaking-change guard and CI workflow. I would finish with the shared-repository limitation and the exact production extension.
Frequently Asked Questions
What should a contract testing take home assignment include?
Include a minimal consumer contract, real consumer parsing test, provider verification, one breaking-change case, clear setup instructions, and CI automation. State assumptions and limitations so reviewers can judge the evidence accurately.
Should I use Pact for a contract testing take-home?
Use Pact when the prompt requires consumer-driven contracts or expects broker-backed verification. For a short tool-neutral exercise, a versioned schema verified from both consumer and provider sides can demonstrate the core reasoning with less infrastructure.
How many endpoints should a take-home contract project cover?
One high-value interaction with success, error, and evolution coverage is often enough. Add endpoints only when they demonstrate a different compatibility risk rather than repeating the same pattern.
Can contract tests replace integration tests?
No. Contract tests focus on boundary compatibility, while integration tests cover real credentials, networking, databases, gateways, and configuration. Explain which evidence each layer contributes.
How do I demonstrate a breaking contract change?
Pass an intentionally incompatible payload to the contract and assert that validation fails at the expected field. A type change from integer cents to a formatted price string is a clear, reviewable example.
What should I put in the README?
Start with the risk protected, prerequisites, install command, test command, and expected result. Follow with architecture, assumptions, coverage limits, and the next production step.
How long should I spend on a contract testing assignment?
Respect the employer's stated timebox and complete the core interaction before adding polish. If no limit is given, record your own timebox and list deliberately deferred enhancements.