Api contract testing
Skill almasumdev/awesome-mobile-testing-agent-skills/.github/skills/integration/api-contract-testing
Agent skills for unit, widget, UI, and end-to-end testing of mobile apps across platforms.
npx -y skills add almasumdev/awesome-mobile-testing-agent-skills --skill api-contract-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Expert guidance on consumer-driven contract tests for mobile apps using Pact. Use when mobile clients depend on backend APIs that evolve independently and staging smoke tests are slow or flaky.
SKILL.md
5.8 KB, as published. Nobody here has run it
API Contract Testing for Mobile Clients
Instructions
Mobile apps ship asynchronously from their backend. A response-shape change the server team considers "non-breaking" can crash 4 % of installed apps a week later. Consumer-driven contract testing replaces fragile "run the app against staging" checks with fast, deterministic contracts that both sides verify in CI.
1. The Model
- The consumer (mobile app) declares what it needs from each endpoint: request shape, expected response shape, status codes.
- Those expectations are serialized as a pact file (JSON) and published to a broker.
- The provider (backend) replays each interaction against its real service in its own CI and fails if it cannot satisfy the contract.
Pact is the de-facto tool. This skill focuses on the consumer side (mobile).
2. Kotlin (Android / KMP) — Pact-JVM Consumer
@ExtendWith(PactConsumerTestExt::class)
@PactTestFor(providerName = "orders-api", pactVersion = PactSpecVersion.V4)
class OrdersApiPactTest {
@Pact(consumer = "android-app")
fun getOrderPact(builder: PactDslWithProvider): RequestResponsePact =
builder
.given("order 42 exists")
.uponReceiving("get order 42")
.path("/orders/42")
.method("GET")
.willRespondWith()
.status(200)
.headers(mapOf("Content-Type" to "application/json"))
.body(newJsonBody { o ->
o.numberType("id", 42)
o.stringType("status", "PAID")
o.numberType("totalCents", 1999)
}.build())
.toPact()
@Test
fun `client parses order`(mockServer: MockServer) = runTest {
val client = OrdersClient(baseUrl = mockServer.getUrl())
val order = client.getOrder(42)
assertEquals(Order(id = 42, status = PAID, total = Money.cents(1999)), order)
}
}
Publish the resulting pact file to a Pact Broker (pact_broker, Pactflow) from CI.
3. Swift (iOS) — PactSwift
final class OrdersApiPactTests: XCTestCase {
var mockService: MockService!
override func setUp() {
mockService = MockService(consumer: "ios-app", provider: "orders-api")
}
func test_getOrder_parsesBody() throws {
mockService
.uponReceiving("a request for order 42")
.given("order 42 exists")
.withRequest(method: .GET, path: "/orders/42")
.willRespondWith(status: 200, body: [
"id": Matcher.SomethingLike(42),
"status": Matcher.SomethingLike("PAID"),
"totalCents": Matcher.SomethingLike(1999),
])
mockService.run { baseURL, done in
Task {
let client = OrdersClient(baseURL: baseURL)
let order = try await client.getOrder(id: 42)
XCTAssertEqual(order, .init(id: 42, status: .paid, totalCents: 1999))
done()
}
}
}
}
4. Dart (Flutter) — pact_dart
void main() {
final pact = PactMockService('flutter-app', 'orders-api');
setUpAll(pact.setup);
tearDownAll(pact.tearDown);
test('client parses order', () async {
pact
.newInteraction()
.given('order 42 exists')
.uponReceiving('get order 42')
.withRequest(method: 'GET', path: '/orders/42')
.willRespondWith(status: 200, body: {
'id': 42, 'status': 'PAID', 'totalCents': 1999,
});
final client = OrdersClient(baseUrl: pact.mockServerUrl);
expect(await client.getOrder(42),
Order(id: 42, status: OrderStatus.paid, totalCents: 1999));
});
}
5. TypeScript (React Native) — @pact-foundation/pact
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
const { like } = MatchersV3;
const provider = new PactV3({ consumer: 'rn-app', provider: 'orders-api' });
test('client parses order', async () => {
provider
.given('order 42 exists')
.uponReceiving('get order 42')
.withRequest({ method: 'GET', path: '/orders/42' })
.willRespondWith({
status: 200,
body: { id: like(42), status: like('PAID'), totalCents: like(1999) },
});
await provider.executeTest(async (mock) => {
const client = new OrdersClient(mock.url);
expect(await client.getOrder(42)).toEqual({ id: 42, status: 'PAID', totalCents: 1999 });
});
});
6. Matchers
Match on types and presence, not exact values, or the contract becomes a wire-format diff. Use like(...), eachLike(...), iso8601Date(), uuid(). Reserve exact matches for enums and stable identifiers.
7. Publishing & Versioning
- Publish pact files tagged with the consumer's git SHA and branch.
- The provider's CI uses
can-i-deployto refuse deploying if it breaks a pact a deployed consumer still needs. - Match pact branches to app release branches so hotfix branches verify against matching provider versions.
8. What Contracts Do NOT Cover
- End-to-end flows across multiple services (use a thin E2E suite).
- Performance, auth, or rate limits (separate tooling).
- UI correctness (snapshot / UI tests).
9. Checklist
- Every screen-level API call used by the mobile client has at least one consumer pact.
- Matchers describe types and structure, not hard-coded values, except for enums and IDs.
- Pact files are published to a broker from CI with version tags.
- Provider CI runs
pact-verifyandcan-i-deployon every merge. - Contract tests replace — not duplicate — broad staging integration smoke tests.
- Legacy endpoints that have been retired are removed from the consumer pact.