surpher / PactSwift

A Swift version of Pact. Implements Pact Specification Version 3.
https://pact.io
MIT License
50 stars 16 forks source link

Question: Is it possible to use generators in requests? #104

Open vkaramov opened 1 year ago

vkaramov commented 1 year ago

❔ Question

I want to use generators in requests like this:

    func testSubscription() throws {

        mockService
            .uponReceiving("Dx subscription")
            .given("good state")
            .withRequest(
                method: PactHTTPMethod.POST,
                path: "/api/v1/subscription",
                headers: [
                    "Content-Type": "application/json"
                ],
                body: [
                    "application": ExampleGenerator.RandomString(size: 20),
                    "token": ExampleGenerator.RandomString(size: 20),
                    "applicationVersion": ExampleGenerator.RandomString(size: 20),
                    "deviceName": ExampleGenerator.RandomString(size: 20),
                    "platform": ExampleGenerator.RandomString(size: 20),
                    "platformVersion": ExampleGenerator.RandomString(size: 20),
                    "language": ExampleGenerator.RandomString(size: 20),
                ]
            )
            .willRespondWith(
                status: 200,
                headers: [
                    "Content-Type": "application/json"
                ],
                body: Matcher.EachLike(
                    [
                        "errors": Matcher.MatchNull(),
                        "success": Matcher.SomethingLike(true)
                    ],
                    min: 1
                )
            )

        let request = DxSubscriptionRequest(
            application: "com.our.company",
            token: "some_token",
            applicationVersion: "1.0.0",
            deviceName: "iPhone 14 pro",
            platform: "iOS",
            platformVersion: "16.1",
            language: "en"
        )

        mockService.run { baseURL, done in

            let config = APIConfiguration(
                baseURL: baseURL,
                channel: "",
                userAgent: "",
                appVersion: request.applicationVersion,
                referer: "",
                xchannel: "MOBILE_NATIVE_IOS",
                token: "",
                accountId: "")

            let subscription = DxSubscription.request(r: request, config: config)

            // Run POST /api/v1/subscription
            NetworkClient()
                .request(endpoint: subscription) { (response: [DxSubscriptionResponse]?, error) in
                    guard error == nil else {
                        XCTFail("Failed with \(String(describing: error))")
                        done()
                        return
                    }

                    do {
                        let dxResponse = try XCTUnwrap(response)

                        XCTAssertNil(dxResponse.first?.errors)
                        XCTAssertTrue(dxResponse.first?.success ?? false)
                    } catch {
                        XCTFail("Expected DxSubscriptionResponse")
                    }

                    done()
                }
        }
    }

but the test fails with errors like:

Expected 'RGpibFlTHTnV1ywjejkA' to be equal to 'com.our.company' - Body does not match the expected body definition

To fix that I had to replace

                body: [
                    "application": ExampleGenerator.RandomString(size: 20),
                    "token": ExampleGenerator.RandomString(size: 20),
                    "applicationVersion": ExampleGenerator.RandomString(size: 20),
                    "deviceName": ExampleGenerator.RandomString(size: 20),
                    "platform": ExampleGenerator.RandomString(size: 20),
                    "platformVersion": ExampleGenerator.RandomString(size: 20),
                    "language": ExampleGenerator.RandomString(size: 20),
                ]

with

                    "application": Matcher.SomethingLike("string"),
                    "token": Matcher.SomethingLike("string"),
                    "applicationVersion": Matcher.SomethingLike("string"),
                    "deviceName": Matcher.SomethingLike("string"),
                    "platform": Matcher.SomethingLike("string"),
                    "platformVersion": Matcher.SomethingLike("string"),
                    "language": Matcher.SomethingLike("string"),

Is it possible to use generators like in JVM version?

@ExtendWith(PactConsumerTestExt::class)
@PactTestFor(providerName = "dx_provider", pactVersion = PactSpecVersion.V3)
class DxTest {
    @Pact(provider = "dx_provider", consumer = "dx_android_consumer")
    fun httpInteraction(builder: PactDslWithProvider): RequestResponsePact {
        return builder
            .given("good state")
            .uponReceiving("Dx subscription")
            .path("/api/v1/subscription")
            .method("POST")
            .headers(mapOf("Content-Type" to "application/json"))
            .body(
                LambdaDsl.newJsonBody { b ->
                    b.stringType("application")
                    b.stringType("token")
                    b.stringType("applicationVersion")
                    b.stringType("deviceName")
                    b.stringType("platform")
                    b.stringType("platformVersion")
                    b.stringType("language")
                }.build()
            )
            .willRespondWith()
            .status(200)
            .headers(mapOf("Content-Type" to "application/json"))
            .body(
                LambdaDsl.newJsonBody { b ->
                    b.nullValue("errors")
                    b.booleanType("success", true)
                }.build()
            )
            .toPact()
    }

Compare generated pacts:

PactSwift:

      "request": {
        "body": {
          "application": "string",
          "applicationVersion": "string",
          "deviceName": "string",
          "language": "string",
          "platform": "string",
          "platformVersion": "string",
          "token": "string"
        },
        "headers": {
          "Content-Type": "application/json"
        },
        "matchingRules": {
          "body": {
            "$.application": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            },
...
        },

JVM:

     "request": {
        "body": {
          "application": "string",
          "applicationVersion": "string",
          "deviceName": "string",
          "language": "string",
          "platform": "string",
          "platformVersion": "string",
          "token": "string"
        },
        "generators": {
          "body": {
            "$.application": {
              "size": 20,
              "type": "RandomString"
            },
            "$.applicationVersion": {
              "size": 20,
              "type": "RandomString"
            },
            "$.deviceName": {
              "size": 20,
              "type": "RandomString"
            },
            "$.language": {
              "size": 20,
              "type": "RandomString"
            },
            "$.platform": {
              "size": 20,
              "type": "RandomString"
            },
            "$.platformVersion": {
              "size": 20,
              "type": "RandomString"
            },
            "$.token": {
              "size": 20,
              "type": "RandomString"
            }
          }
        },
        "headers": {
          "Content-Type": "application/json"
        },
        "matchingRules": {
          "body": {
            "$.application": {
              "combine": "AND",
              "matchers": [
                {
                  "match": "type"
                }
              ]
            },
...

💬 Context

Xcode 14.2

surpher commented 1 year ago

What version of PactJVM are you using?

Had a look at what the PactSwift's pact structure looks like using your example. The structure and all seems correct when sending the pact model to pact_ffi. Must be an issue in pact_ffi verifier not honouring the generators object when verifying the request.

            "request": {
                "path": "/api/species",
                "body": {
                    "deviceName": "WppxFhJaX3TAhzTKZakW",
                    "platform": "pqZtJuwTcArF7uGe6L1d",
                    "platformVersion": "QSb5cMfpGXo8ul3HCpXK",
                    "language": "ZAjcMhbPXh9LDf9SsE1r",
                    "application": "JE4FtUcRH4uBDl0x5wu6",
                    "applicationVersion": "9qrmoscqbYd2cGcUq5FD",
                    "token": "XcunuysQYixQhGYn1Xdd"
                },
                "method": "post",
                "headers": {
                    "Content-Type": "application/json"
                },
                "generators": {
                    "body": {
                        "$.platform": {
                            "type": "RandomString",
                            "size": 20
                        },
                        "$.deviceName": {
                            "type": "RandomString",
                            "size": 20
                        },
                        "$.token": {
                            "type": "RandomString",
                            "size": 20
                        },
                        "$.language": {
                            "type": "RandomString",
                            "size": 20
                        },
                        "$.applicationVersion": {
                            "type": "RandomString",
                            "size": 20
                        },
                        "$.application": {
                            "type": "RandomString",
                            "size": 20
                        },
                        "$.platformVersion": {
                            "type": "RandomString",
                            "size": 20
                        }
                    }
                }
            },
            "response": {
...
vkaramov commented 1 year ago

Thank you, I'm using Pact JVM 4.3.18.

surpher commented 1 year ago

I'll ask the team working on pact_ffi about it, but can't necessarily promise anything. This "might" be fixed with the new PactSwift version that's currently in the works on a fork.

It will take advantage of Apple's concurrency and will slightly change how the DSL works. As a bit of a background v1 creates a pact model and sends it to pact_ffi (the Pact business logic/verifier/mock server written in Rust and shared across language implementations). The new PactSwift v2 will send each of the interactions into pact_ffi via a handler. V2 will essentially shift responsibility into pact_ffi. So not sure if that will help you with your project.

No timelines for when it will be available though!

vkaramov commented 1 year ago

@surpher Great, thanks!