Giter Club home page Giter Club logo

pactswift's Introduction

PactSwift

Build codecov MIT License PRs Welcome! slack Twitter

PactSwift logo

This framework provides a Swift DSL for generating and verifying Pact contracts. It provides the mechanism for Consumer-Driven Contract Testing between dependent systems where the integration is based on HTTP. PactSwift allows you to test the communication boundaries between your app and services it integrates with.

PactSwift implements Pact Specification v3 and runs the mock service "in-process". No need to set up any external mock services, stubs or extra tools πŸŽ‰. It supports contract creation along with client verification. It also supports provider verification and interaction with a Pact broker.

Installation

Note: see Upgrading for notes on upgrading and breaking changes.

Swift Package Manager

Xcode

  1. Enter https://github.com/surpher/PactSwift in Choose Package Repository search bar
  2. Optionally set a minimum version when Choosing Package Options
  3. Add PactSwift to your test target. Do not embed it in your application target.

Package.swift

dependencies: [
    .package(url: "https://github.com/surpher/PactSwift.git", .upToNextMinor(from: "0.11.0"))
]

Linux

Linux Installation Instructions

When using PactSwift on a Linux platform you will need to compile your own libpact_ffi.so library for your Linux distribution from pact-reference/rust/pact_ffi or fetch a Pact FFI Library x.y.z from pact-reference releases.

It is important that the version of libpact_ffi.so you build or fetch is compatible with the header files provided by PactMockServer. See release notes for details.

See /Scripts/build_libpact_ffi for some inspiration building libraries from Rust code. You can also go into pact-swift-examples and look into the Linux example projects. There is one for consumer tests and one for provider verification. They contain the GitHub Workflows where building a pact_ffi .so binary and running Pact tests is automated with scripts.

When testing your project you can either set LD_LIBRARY_PATH pointing to the folder containing your libpact_ffi.so:

export LD_LIBRARY_PATH="/absolute/path/to/your/rust/target/release/:$LD_LIBRARY_PATH"
swift build
swift test -Xlinker -L/absolute/path/to/your/rust/target/release/

or you can move your libpact_ffi.so into /usr/local/lib:

mv /path/to/target/release/libpact_ffi.so /usr/local/lib/
swift build
swift test -Xlinker -L/usr/local/lib/

NOTE:

Writing Pact tests

  • Instantiate a MockService object by defining pacticipants,
  • Define the state of the provider for an interaction (one Pact test),
  • Define the expected request for the interaction,
  • Define the expected response for the interaction,
  • Run the test by making the API request using your API client and assert what you need asserted,
  • When running on CI share the generated Pact contract file with your provider (eg: upload to a Pact Broker),
  • When automating deployments in a CI step run can-i-deploy and if computer says OK, deploy with confidence!

Example Consumer Tests

import XCTest
import PactSwift

@testable import ExampleProject

class PassingTestsExample: XCTestCase {

  static var mockService = MockService(consumer: "Example-iOS-app", provider: "some-api-service")

  // MARK: - Tests

  func testGetUsers() {
    // #1 - Declare the interaction's expectations
    PassingTestsExample.mockService

      // #2 - Define the interaction description and provider state for this specific interaction
      .uponReceiving("A request for a list of users")
      .given(ProviderState(description: "users exist", params: ["first_name": "John", "last_name": "Tester"])

      // #3 - Declare what our client's request will look like
      .withRequest(
        method: .GET,
        path: "/api/users",
      )

      // #4 - Declare what the provider should respond with
      .willRespondWith(
        status: 200,
        headers: nil, // `nil` means we don't care what the headers returned from the API are.
        body: [
          "page": Matcher.SomethingLike(1), // We expect an Int, 1 will be used in the unit test
          "per_page": Matcher.SomethingLike(20),
          "total": ExampleGenerator.RandomInt(min: 20, max: 500), // Expecting an Int between 20 and 500
          "total_pages": Matcher.SomethingLike(3),
          "data": Matcher.EachLike( // We expect an array of objects
            [
              "id": ExampleGenerator.RandomUUID(), // We can also use random example generators
              "first_name": Matcher.SomethingLike("John"),
              "last_name": Matcher.SomethingLike("Tester"),
              "renumeration": Matcher.DecimalLike(125_000.00)
            ]
          )
        ]
      )

    // #5 - Fire up our API client
    let apiClient = RestManager()

    // Run a Pact test and assert **our** API client makes the request exactly as we promised above
    PassingTestsExample.mockService.run(timeout: 1) { [unowned self] mockServiceURL, done in

      // #6 - _Redirect_ your API calls to the address MockService runs on - replace base URL, but path should be the same
      apiClient.baseUrl = mockServiceURL

      // #7 - Make the API request.
      apiClient.getUsers() { users in

          // #8 - Test that **our** API client handles the response as expected. (eg: `getUsers() -> [User]`)
          XCTAssertEqual(users.count, 20)
          XCTAssertEqual(users.first?.firstName, "John")
          XCTAssertEqual(users.first?.lastName, "Tester")

        // #9 - Always run the callback. Run it in your successful and failing assertions!
        // Otherwise your test will time out.
        done()
      }
    }
  }

  // Another Pact test example...
  func testCreateUser() {
    PassingTestsExample.mockService
      .uponReceiving("A request to create a user")
      .given(ProviderState(description: "user does not exist", params: ["first_name": "John", "last_name": "Appleseed"])
      .withRequest(
        method: .POST,
        path: Matcher.RegexLike("/api/group/whoopeedeedoodah/users", term: #"^/\w+/group/([a-z])+/users$"#),
        body: [
          // You can use matchers and generators here too, but are an anti-pattern.
          // You should be able to have full control of your requests.
          "first_name": "John",
          "last_name": "Appleseed"
        ]
      )
      .willRespondWith(
        status: 201,
        body: [
          "identifier": Matcher.FromProviderState(parameter: "userId", value: .string("123e4567-e89b-12d3-a456-426614174000")),
          "first_name": "John",
          "last_name": "Appleseed"
        ]
      )

   let apiClient = RestManager()

    PassingTestsExample.mockService.run { mockServiceURL, done in
     // trigger your network request and assert the expectations
     done()
    }
  }
  // etc.
}

MockService holds all the interactions between your consumer and a provider. For each test method, a new instance of XCTestCase class is allocated and its instance setup is executed. That means each test has it's own instance of var mockService = MockService(). Hence the reason we're using a static var mockService here to keep a reference to one instance of MockService for all the Pact tests. Alternatively you could wrap your mockService into a singleton.
Suggestions to improve this are welcome! See contributing.

References:

Generated Pact contracts

By default, generated Pact contracts are written to /tmp/pacts. If you want to specify a directory you want your Pact contracts to be written to, you can pass a URL object with absolute path to the desired directory when instantiating your MockService (Swift only):

MockService(
    consumer: "consumer",
    provider: "provider",
    writePactTo: URL(fileURLWithPath: "/absolute/path/pacts/folder", isDirectory: true)
)

Alternatively you can define a PACT_OUTPUT_DIR environment variable (in Run section of your scheme) with the path to directory you want your Pact contracts to be written into.

PactSwift first checks whether URL has been provided when initializing MockService object. If it is not provided it will check for PACT_OUTPUT_DIR environment variable. If env var is not set, it will attempt to write your Pact contract into /tmp/pacts directory.

Note that sandboxed apps (macOS apps) are limited in where they can write Pact contract files to. The default location seems to be the Documents folder in the sandbox (eg: ~/Library/Containers/xyz.example.your-project-name/Data/Documents). Setting the environment variable PACT_OUTPUT_DIR might not work without some extra leg work tweaking various settings. Look at the logs in debug area for the Pact file location.

Sharing Pact contracts

If your setup is correct and your tests successfully finish, you should see the generated Pact files in your nominated folder as _consumer_name_-_provider_name_.json.

When running on CI use the pact-broker command line tool to publish your generated Pact file(s) to your Pact Broker or a hosted Pact broker service. That way your API-provider team can always retrieve them from one location, set up web-hooks to trigger provider verification tasks when pacts change. Normally you do this regularly in you CI step/s.

See how you can use a simple Pact Broker Client in your terminal (CI/CD) to upload and tag your Pact files. And most importantly check if you can safely deploy a new version of your app.

Provider verification

In your unit tests suite, prepare a Pact Provider Verification unit test:

  1. Start your local Provider service
  2. Optionally, instrument your API with ability to configure provider states
  3. Run the Provider side verification step

To dynamically retrieve pacts from a Pact Broker for a provider with token authentication, instantiate a PactBroker object with your configuration:

// The provider being verified
let provider = ProviderVerifier.Provider(port: 8080)

// The Pact broker configuration
let pactBroker = PactBroker(
  url: URL(string: "https://broker.url/")!,
  auth: auth: .token(PactBroker.APIToken("auth-token")),
  providerName: "Your API Service Name"
)

// Verification options
let options = ProviderVerifier.Options(
  provider: provider,
  pactsSource: .broker(pactBroker)
)

// Run the provider verification task
ProviderVerifier().verify(options: options) {
  // do something (eg: shutdown the provider)
}

To validate Pacts from local folders or specific Pact files use the desired case.

Examples
// All Pact files from a directory
ProviderVerifier()
  .verify(options: ProviderVerifier.Options(
    provider: provider,
    pactsSource: .directories(["/absolute/path/to/directory/containing/pact/files/"])
  ),
  completionBlock: {
    // do something
  }
)
// Only the specific Pact files
pactsSource: .files(["/absolute/path/to/file/consumerName-providerName.json"])
// Only the specific Pact files at URL
pactsSource: .urls([URL(string: "https://some.base.url/location/of/pact/consumerName-providerName.json")])

Submitting verification results

To submit the verification results, provide PactBroker.VerificationResults object to pactBroker.

Example

Set the provider version and optional provider version tags. See version numbers for best practices on Pact versioning.

let pactBroker = PactBroker(
  url: URL(string: "https://broker.url/")!,
  auth: .token("auth-token"),
  providerName: "Some API Service",
  publishResults: PactBroker.VerificationResults(
    providerVersion: "v1.0.0+\(ProcessInfo.processInfo.environment["GITHUB_SHA"])",
    providerTags: ["\(ProcessInfo.processInfo.environment["GITHUB_REF"])"]
  )
)

For a full working example of Provider Verification see Pact-Linux-Provider project in pact-swift-examples repository.

Matching

In addition to verbatim value matching, you can use a set of useful matching objects that can increase expressiveness and reduce brittle test cases.

See Wiki page about Matchers for a list of matchers PactSwift implements and their basic usage.

Or peek into /Sources/Matchers/.

Example Generators

In addition to matching, you can use a set of example generators that generate random values each time you run your tests.

In some cases, dates and times may need to be relative to the current date and time, and some things like tokens may have a very short life span.

Example generators help you generate random values and define the rules around them.

See Wiki page about Example Generators for a list of example generators PactSwift implements and their basic usage.

Or peek into /Sources/ExampleGenerators/.

Objective-C support

PactSwift can be used in your Objective-C project with a couple of limitations, (e.g. initializers with multiple optional arguments are limited to only one or two available initializers). See Demo projects repository for more examples.

_mockService = [[PFMockService alloc] initWithConsumer: @"Consumer-app"
                                              provider: @"Provider-server"
                                      transferProtocol: TransferProtocolStandard];

PF stands for Pact Foundation.

Please feel free to raise any issues as you encounter them, thanks.

Demo projects

PactSwift - Consumer PactSwift - Provider

See pact-swift-examples for more examples of how to use PactSwift.

Contributing

See:

Acknowledgements

This project takes inspiration from pact-consumer-swift and pull request Feature/native wrapper PR.

Logo and branding images provided by @cjmlgrto.

pactswift's People

Contributors

huwr avatar orj avatar surpher avatar tonyarnold avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

pactswift's Issues

Task: Setup `PactSwift_iOS` to build and use `libpact_mock_server_ffi` library during Xcode Build Phase

❕ Problem Statement

PactSwift takes advantage of pact_mock_server written in Rust. The PactSwift_iOS target in PactSwift currently uses a static library (.a) which contains slices for aarm64 (device) and x86_64 (simulator) architectures. This means this fat static library pact_mock_server blows up in size as it contains doubled up code for each architecture.

Instead of building it and adding it into the repo using LFS, build the static library as part of Xcode Build Phase. This will avoid incurring any unnecessary cost GitHub LFS bandwidth.

Reason why not using a dynamic lib is Carthage. When building the dependency with carthage, carthage wants to build for device but dylib only handles simulator and carthage build fails.

πŸ’¬ Task Description

Replace use of static library for PactSwift_iOS with a dummy .a file, build the static lib using cargo lipo --release during a BuildPhase and replace the dummy .a file with the build static lib.

πŸ‘©β€πŸ”§ Technical Design Notes

  • Requires cargo-lipo to be installed in order to build a universal binary for iOS.
  • Build time increases quite dramatically but that should be a rare occasion (when running carthage build). Having quick feedback during CI build/test run is welcome, but keeping PactSwift available free of charge and also avoiding unnecessary charges outweighs the time required to build on CI/CD machine.
  • Will require users of PactSwift to have Rust-lang installed - minor inconvenience running brew install rust and outweighs the size of static libs and bandwidth consumption.

🀝 Relationships

  • Other Related Issues: #41

Bug: Building mock server failing on cargo lipo

🌎 Environment

  • Xcode: 12.4
  • Platform: iOS, macOS
  • Version/Release: rust nightly toolchain
  • Dependency manager: n/a

πŸ’¬ Description

When building the mock server as part of the build process, the cargo lipo command fails due to a bug in Rust.

🦢 Reproduction Steps

Steps to reproduce the behavior:

  1. Clone this repo
  2. Open Xcode
  3. Build project

πŸ€” Expected Results

Project builds

😲 Actual Results

Project build fails

🌳 Logs

Compiling pact_verifier v0.10.3 (/Users/marko/Developer/pact-foundation/pact-reference/rust/pact_verifier)
warning: dropping unsupported crate type `cdylib` for target `aarch64-apple-ios`
warning: unused variable: `verification_context`
  --> pact_verifier/src/messages.rs:19:3
   |
19 |   verification_context: &HashMap<&str, Value>
   |   ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_verification_context`
   |
   = note: `#[warn(unused_variables)]` on by default
warning: 2 warnings emitted
error: internal compiler error: failed to process buffered lint here
   --> pact_verifier/src/pact_broker.rs:864:20
    |
864 |           Ok(_) => debug!("Pushed tag {} for provider version {}", tag, version),
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: delayed at /rustc/04caa632dd10c2bf64b69524c7f9c4c30a436877/compiler/rustc_lint/src/early.rs:384:18
    = note: this error: internal compiler error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: internal compiler error: failed to process buffered lint here
   --> pact_verifier/src/lib.rs:825:16
    |
825 |       Ok(_) => log::info!("Results published to Pact Broker"),
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: delayed at /rustc/04caa632dd10c2bf64b69524c7f9c4c30a436877/compiler/rustc_lint/src/early.rs:384:18
    = note: this error: internal compiler error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: internal compiler error: failed to process buffered lint here
   --> pact_verifier/src/lib.rs:826:23
    |
826 |       Err(ref err) => log::error!("Publishing of verification results failed with an error: {}", err)
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: delayed at /rustc/04caa632dd10c2bf64b69524c7f9c4c30a436877/compiler/rustc_lint/src/early.rs:384:18
    = note: this error: internal compiler error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
thread 'rustc' panicked at 'no errors encountered even though `delay_span_bug` issued', compiler/rustc_errors/src/lib.rs:974:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: internal compiler error: unexpected panic
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md
note: rustc 1.51.0-nightly (04caa632d 2021-01-30) running on x86_64-apple-darwin
note: compiler flags: -C opt-level=3 -C embed-bitcode=no --crate-type cdylib --crate-type rlib
note: some of the compiler flags provided by cargo are hidden
query stack during panic:
end of query stack
error: could not compile `pact_verifier`
To learn more, run the command again with --verbose.
[ERROR cargo_lipo] Failed to build "pact_verifier_ffi" for "aarch64-apple-ios": Executing "/Users/marko/.rustup/toolchains/nightly-x86_64-apple-darwin/bin/cargo" "--color" "auto" "build" "-p" "pact_verifier_ffi" "--target" "aarch64-apple-ios" "--release" "--lib" finished with error status: exit code: 101

πŸ“„ Stack Traces

n/a

πŸ‘€ Possible cause

Using cargo 1.51.0-nightly (c3abcfe8a 2021-01-25)

🀝 Relationships

  • Related PRs or Issues: #xxx, #yyy

Task: Xcode 12 issue - `libpact_mock_server.a` missing arm64 architecture

❕ Problem Statement

Xcode 12 is about to be released and compiler is building for the upcoming arm64 architecture. When having command line tools set to Xcode 12 and running carthage build [--no-skip-current], the build fails with:

error: The linked library 'libpact_mock_server.a' is missing one or more architectures required by this target: arm64. (in target 'PactSwift_macOS' from project 'PactSwift')

πŸ’¬ Task Description

Build new libpact_mock_server.a static libs that include arm64 architecture.
DTK poking it’s head up when building with Xcode 12 due to new arm64 architecture missing in libpact_mock_server.a.

πŸ‘©β€πŸ”§ Technical Design Notes

Carthage v0.35.0
Xcode 12 beta 6,
Xcode 12 Command line tools

🀝 Relationships

Bug: Matcher in request body crashes at `PactSwift.PactBuilder.process(element)`

🌎 Environment

  • Xcode: 12
  • Platform: macOS libpact_mock_server_ffi.dylib and iOS using libpact_mock_server.a
  • Version/Release: any
  • Dependency manager: carthage

πŸ’¬ Description

When writing a pact test with a matcher in a request body, the PactSwift framework throws (and crashes) stating body contains non-encodable element. Crashes when setting up the interaction, specifically .withRequest(body: [...])

🦢 Reproduction Steps

Steps to reproduce the behavior:

  1. Install rust brew install rust (required to compile libpact_mock_server_ffi.dylib at carthage update step)
  2. Clone surpher/pact-swift-examples repo
  3. Change directory into pact-swift-examples/Pact-macOS-Example
  4. Update Cartfile to point to `github "surpher/PactSwift" "tech/macos-dylib"
  5. Run carthage update
  6. Open Pact-macOS-Example project in Xcode
  7. Run unit test: testCreateUser_WithBodyThatMatchesAType()

πŸ€” Expected Results

Pact test runs and asserts the expected statusCode is 201

😲 Actual Results

Pact test runs and crashes with Can not instantiate a Request with non-encodable body.

🌳 Logs

2020-10-14 16:38:52.568071+1100 Pact-macOS-Example[34770:14528492] ApplePersistenceIgnoreState: Existing state will not be touched. New state will be written to (null)
2020-10-14 16:38:54.350601+1100 Pact-macOS-Example[34770:14528492] NSDocumentController Info.plist warning: The values of CFBundleTypeRole entries must be 'Editor', 'Viewer', 'None', or 'Shell'.
Test Suite 'Selected tests' started at 2020-10-14 16:38:54.721
Test Suite 'Pact-macOS-ExampleTests.xctest' started at 2020-10-14 16:38:54.722
Test Suite 'Pact_macOS_Example_CarthageTests' started at 2020-10-14 16:38:54.722
Test Case '-[Pact_macOS_ExampleTests.Pact_macOS_Example_CarthageTests testCreateUser_WithBodyThatMatchesAType]' started.
2020-10-14 16:38:54.797824+1100 Pact-macOS-Example[34770:14528985] [Arbitration] starting DTServiceHub child handshake.0 (send: 0x800b, receive: 0xab07)
2020-10-14 16:38:54.798057+1100 Pact-macOS-Example[34770:14528492] [Arbitration] attempting connection to singleton: 34747 with send port: 0x15103
2020-10-14 16:38:54.798301+1100 Pact-macOS-Example[34770:14528492] [Arbitration] handshake SUCCESSFUL (child: 34773 -> singleton: 34747)
2020-10-14 16:38:54.802973+1100 Pact-macOS-Example[34770:14528492] PactSwift: Error casting '{
    name = "PactSwift.Matcher.SomethingLike(value: \"Julia\", rules: [[\"match\": PactSwift.AnyEncodable(_encode: (Function))]])";
}' to a JSON safe Type: String, Int, Double, Decimal, Bool, Dictionary<String, Encodable>, Array<Encodable>):
Fatal error: Can not instantiate a `Request` with non-encodable `body`.: file /Users/marko/Developer/pact-foundation/pact-swift-examples/Pact-macOS-Example/Carthage/Checkouts/PactSwift/Sources/Definitions/Request.swift, line 74
2020-10-14 16:38:54.803340+1100 Pact-macOS-Example[34770:14528492] Fatal error: Can not instantiate a `Request` with non-encodable `body`.: file /Users/marko/Developer/pact-foundation/pact-swift-examples/Pact-macOS-Example/Carthage/Checkouts/PactSwift/Sources/Definitions/Request.swift, line 74
(lldb) 

πŸ“„ Stack Traces

n/a

🀝 Relationships

Task: Switch from discrete framework bundles to XCFramework

❕ Problem Statement

PactSwift builds a discrete framework for iOS and macOS platforms.
With introduction of Apple Silicon, building and using XCFrameworks is a hard requirement.

πŸ’¬ Task Description

Refactor project settings and targets to build platform-independent XCFrameworks (Xcode 12 and above)

πŸ‘©β€πŸ”§ Technical Design Notes

  • TBD

🀝 Relationships

  • Other Related Issues: #xxx, #yyy

Task: PactSwift sends empty objects for `matchers` and `generators` to `libpact_mock_server`

🌎 Environment

  • Xcode: 11.7
  • Platform: all
  • Version/Release: master
  • Dependency manager: n/a

πŸ’¬ Description

When traversing the Pact DSL and building the data object to be sent to libpact_mock_server in PactBuilder.swift, it always instantiates an empty dictionary in which matchers and generators are stored when traversing through DSL. If no matchers are found, it still returns an empty dictionary. This should potentially reduce the risk of experiencing unexpected behaviour when interacting with libpact_mock_server.

Check for empty dictionary before returning from PactBuilder().encoded(for:)

🦢 Reproduction Steps

Steps to reproduce the behavior:

  1. Set global env PACT_ENABLE_LOGGING: true or alternatively if you want to manually check what data is sent to libpact_mock_server, set a breakpoint when entered MockServer.setup(pact:protocol:completion) and validate pact data using `String(data: pact, encoding: .utf8):
  2. Write a simple Pact test with no matchers and generators
  3. Run test
  4. Observe the debug console for generated interactions

πŸ’‰ Test Data

mockService
  .uponReceiving("Request for a list")
  .given("elements do not exist")
  .withRequest(method: .GET, path: "/elements")
  .willRespondWith(
    status: 200,
    body: ["elements":[]]
  )

πŸ€” Expected Results

Interaction node does not create and send empty body object for matchingRules and generators nodes to libpact_mock_server

😲 Actual Results

Node matchingRules and generators are sent to libpact_mock_server

🌳 Logs

When running The above test example, the following Pact data is generated and logged in debug (only part of interest extracted):

    "interactions": [{
		"interactionDescription": "Request for a list",
		"response": {
			"status": 200,
			"body": {
				"elements": []
			},
			"generators": { <-- BAD
				"body": {}
			},
			"matchingRules": { <-- BAD
				"body": {}
			}
		},

🀝 Relationships

  • Related PRs or Issues:

Example Generator: DateTime

Implement an Example Generator for Random DateTime.

Generates a Date and Time (timestamp) value from the current date and time either in ISO format or using the provided format string
Signature:

RandomDateTime(format: String? = nil)

Example:

`RandomDateTime()`
// generates: 

{ "type": "DateTime", "format": "yyyy/MM/dd - HH:mm:ss.S" }

Example Generator: Date

Implement an Example Generator for Random Date.

Generates a Date value from the current date either in ISO format or using the provided format string.

Signature:

RandomDate(format: String? = nil)

Example:

`RandomDate("MM/dd/yyyy")`
// generates: 

{ "type": "Date", "format": "MM/dd/yyyy" }

Example Generator: RandomDecimal

Implement an Example Generator for Random Decimal.

Generates a random decimal value (BigDecimal) with the provided number of digits.

Signature:

RandomDecimal(digits: Int = 6)

Example:

`RandomDecimal(digits: 6)`
// generates: 

{ "type": "RandomDecimal", "digits": 6 }

Remove the generated JSON file on failed test run

Context

When passing individual interactions to libpact_mock_server.a a partial Pact file is generated. If any of the Pact tests fail while Pact file has already been created and written to the disk, the file is not deleted and contains the successful interactions up to the point of failure.

Issue

Due to the context described, the generated file is incomplete and can be mistaken for a successfully generated Pact contract.

Due to #23 it would make more sense to clean up the $PACT_OUTPUT_DIR when failing the test suite so there is no overwriting possible and a clean Pact contract is written with each .verify() call, reducing the chance of mistaking a partially generated Pact file for a contract with all interactions.

To do

When MockService fails a Pact test, delete any generated JSON file for the Consumer-Provider interactions for which the test has failed.

Benefit

If there is CI set up with automated Pact publish step, an invalid/incomplete Pact contract could be published to the Pact Broker. Cleaning up the $PACT_OUTPUT_DIR folder would provide a failsafe(ish) step where there would be nothing to upload and mess up the versions.

Task: Move Build Phase scripts into script files

❕ Problem Statement

macOS target contains a couple of Build Phase scripts that are written in the Build Phase themselves. This is not ideal and should be pulled out into a script file.

πŸ’¬ Task Description

Pull out the scripts in Build Phases into script files and move into /Scripts/BuildPhases:

Targets:

  • PactSwift_macOS
  • PactSwift_iOS

BuildPhases:

  • Cargo build (libpact_mock_server.a)
  • Move the "check for rust" block of code in ./Scripts/build_file_list_and_swiftlint file into it's own script file to be individually referenced from either a BuildPhase or GitHub workflow step

πŸ‘©β€πŸ”§ Technical Design Notes

  • No scripts that are called from Build Phase are to install anything - it should only check for availability of the tools and stop the build (eg: non zero return from script) if required tools are missing, and clearly display what the problem is.
  • Build Phase script should reference the script file to run
  • See Build Phase Swiftlint and ./Scripts/BuildPhases for guidance
  • Script filenames to not contain extension (eg. .sh) as first line in the script should imply it's a shell script.

🀝 Relationships

  • Other Related Issues/PRs: #41

Support adding Random Generators to Pact JSON file

Depending on where a RandomGenerator is used, add the generators to the appropriate JSON node:

  • Request - body, headers
  • Response - body, headers

Example:

{
  "body": {
    "id": 100,
    "description": "Small pack of bolts",
    "processDate": "2015-07-01"
  },
  "generators": {
      "body": {
          "$.id": {
              "type": "RandomDecimal",
              "digits": 5
          },
          "$.description": {
              "type": "RandomString",
              "size": 20
          },
          "$.processDate": {
              "type": "Date"
          }
      }
  }
}

Task: Rename master branch

❕ Problem Statement

GitHub dropped master in favour of main. New projects already use main as the default branch. Looking into the future, using main will reduce confusion when switching projects.

πŸ’¬ Task Description

Rename master to main.

πŸ‘©β€πŸ”§ Technical Design Notes

🀝 Relationships

  • Other Related Issues:

Task: Build mock server binary with slices for `arch64` and `x86_64`

❕ Problem Statement

Xcode 12.2 requires that binaries used in macOS framework include slices for arm64 (Apple Silicon). This means the build always fails when building PactSwift framework for macOS with Carthage dependency manager and SPM (uses a script in /Scripts/BuildPhases).

NOTE: Unfortunately the cargo build command fails when building a Rust dependency for aarch64-apple-darwin triple on x86_64 host:

error: failed to run custom build command for `ring v0.16.15`

πŸ’¬ Task Description

  • Build the libpact_mock_server.a for x86_64 and arm64 architectures and create a fat library in a phase script.
  • Remove the arm64 as excluded architectures in macOS target (reverts #53).
  • Consider moving PactSwift to XCFramework format

πŸ‘©β€πŸ”§ Technical Design Notes

  • Script that builds the libpact_mock_server.a is in a script file and referenced in the build phase script.
  • Rust guide and pointers:
# install nightly
% rustup install nightly

# update rustup toolchains to 1.23 or newer
% RUSTUP_UPDATE_ROOT=https://dev-static.rust-lang.org/rustup sh <(curl --proto '=https' --tlsv1.2 -sSf https://dev-static.rust-lang.org/rustup/rustup-init.sh) --default-toolchain beta

# set nightly toolchain as default as it contains `aarch64-apple-darwin` toolchain for the x86_64 host
% rustup default nightly-x86_64-apple-darwin

# add the required Rust triples for currently supported architectures
% rustup target add aarch64-apple-ios aarch64-apple-darwin x86_64-apple-ios x86_64-apple-darwin

# build libpact_mock_server.a for arm64 in ./Submodules/pact-reference/rust/pact_mock_server_ffi
% cargo build --target=aarch64-apple-darwin --release

# use lipo to generate a fat binary
% lipo -create <input_file_1> <input_file_2> -output <output_file>

🀝 Relationships

πŸ”— Resources

Bug: Matcher.EachLike generating unexpected values

🌎 Environment

  • Xcode: 12.5
  • Platform: iOS, macOS
  • Version/Release: 0.4.2
  • Dependency manager: /

πŸ’¬ Description

When using Matcher.EachLike the values for it in the generated Pact contract file are returning unexpected values

🦢 Reproduction Steps

Steps to reproduce the behavior:

  1. Prepare a Pact test
  2. Use a couple of nested EachLike matchers:
// Response
body: [
  "array_of_objects": Matcher.EachLike(
    [
      "key_string": Matcher.SomethingLike("String value"),
      "key_int": Matcher.IntegerLike(123),
      "key_array": Matcher.EachLike(
        Matcher.SomethingLike("array_value")
      )
    ]
  ),
  "array_of_strings": Matcher.EachLike(
    Matcher.SomethingLike("A string"),
    min: 1
  )
]
  1. Run Pact test
  2. Observe generated Pact contract file

πŸ€” Expected Results

The body in the generated Pact contract file to contain:

"body": {
  "array_of_objects": {
    "key_array": ["array_value"],
    "key_int": 123,
    "key_string": "String value"
  },
  "array_of_strings": ["A string"]
}

...

"matchingRules": {
  "body": {
    "$.array_of_objects": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type",
          "min": 1
        }
      ]
    },
    "$.array_of_objects[*].*": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type"
        }
      ]
    },
    "$.array_of_objects[*].key_array": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type",
          "min": 1
        }
      ]
    },
    "$.array_of_objects[*].key_int": {
      "combine": "AND",
      "matchers": [
        {
          "match": "integer"
        }
      ]
    },
    "$.array_of_objects[*].key_string": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type"
        }
      ]
    },
    "$.array_of_strings": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type",
          "min": 1
        }
      ]
    }
  }
},

😲 Actual Results

The body in the generated Pact contract file contains:

"body": {
  "array_of_objects": {
    "key_array": "array_value",
    "key_int": 123,
    "key_string": "String value"
  },
  "array_of_strings": "A string"
}

...

"matchingRules": {
  "body": {
    "$.array_of_objects": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type",
          "min": 1
        }
      ]
    },
    "$.array_of_objects.key_array": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type"
        }
      ]
    },
    "$.array_of_objects.key_int": {
      "combine": "AND",
      "matchers": [
        {
          "match": "integer"
        }
      ]
    },
    "$.array_of_objects.key_string": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type"
        }
      ]
    },
    "$.array_of_strings": {
      "combine": "AND",
      "matchers": [
        {
          "match": "type",
          "min": 1
        }
      ]
    }
  }
},
...

🌳 Logs

Include any logs or command output if applicable...

πŸ“„ Stack Traces

Include a stack trace if applicable...

🀝 Relationships

  • Related PRs or Issues: #xxx, #yyy

Task: Look into using dynamic lib instead of static lib

❕ Problem Statement

The MockServer used in this framework is built from a Rust codebase and is built into a static lib (.a). This means we end up with huge lib files (eg: the .a file for iOS is more than 100MB because it includes a slice for iOS simulator and iOS device). This means a lot of traffic on initial and subsequent MockServer updates pulls. That also means that the updated .a lives in GitHub LFS and each pull/push, and triggered GitHub Action eats into the Storage and Bandwidth quota. Far from ideal.

πŸ’¬ Task Description

Because PactSwift should not be included in the App Store build, consider preparing dynamic libs to be used with test target.

It may require a different project setup at user end (for the ones already using it), but if we can reduce our size as a dependency, that's a good trade-off.

πŸ‘©β€πŸ”§ Technical Design Notes

  • Check if we can replace macOS static lib with a dynamic lib

  • Replacing iOS binary could be a separate concern

🀝 Relationships

Example Generator: RandomHexadecimal

Implement an Example Generator for Random Hexadecimal.

Generates a random hexadecimal value (String) with the provided number of digits.

Signature:

RandomHexadecimal(digits: Int = 6)

Example:

`RandomHexadecimal(digits: 6)`
// generates: 

{ "type": "RandomHexadecimal", "digits": 6 }

Task: Split github workflow to run test steps simultaneously

❕ Problem Statement

Currently all the steps run in a sequence which takes a long time to finish testing iOS and macOS targets.

πŸ’¬ Task Description

Split testing iOS and macOS targets into separate jobs that can run simultaneously.

πŸ‘©β€πŸ”§ Technical Design Notes

  • both jobs should rely on the previous set-up steps

🀝 Relationships

  • Other Related Issues:

Improve RandomUUID example generator

πŸ—£ Context

RandomUUID ExampleGenerator is only writing a simple UUID, eg: 22fa4d123c17471bbb6ab57aaa5fb24f into a Pact file. This makes it a bit annoying when expecting uppercased and/or dash separated value.

As an example:

// Let's say Swagger says the value will be a UUID in dashed and uppercased format
// Pact test
// -- code...
body: [
  "id": ExampleGenerator.RandomUUID() // eg: "BDA52033-F0F1-4EC3-8CA4-E04D71572913"
]
// -- code...

PactSwift generates a Pact interaction with the id value of eg: BDA52033-F0F1-4EC3-8CA4-E04D71572913 and passes it to libpact_mock_server_ffi. When FFI is told to generate and write the interactions in a Pact file, a different UUID is generated and written in simple format type, eg: fda98152f0f14ce38aa4b04a54132913.

Because of the above an encodable Swift model that defines a property of type Foundation.UUID fails automatic serialisation unless a custom encodable initialiser is provided. But that could/should be avoided.

πŸ’¬ Narrative

When I use ExampleGenerator.RandomUUID()
I want to set a specific format
So that the client validation is easier

πŸ“ Notes

Even if defining an ExampleGenerator and preparing a Pact file with a specific format for UUID, libpact_mock_server_ffi returns a different UUID in simple format. Will need to work with pact-foundation/pact-reference to support this.

As a workaround until this is implemented, a Matcher.RegexLike(_:term:) can be used instead and providing the UUID value in the expected format.

🎨 Design

N/A

βœ… Acceptance Criteria

GIVEN I use ExampleGenerator.RandomUUID() in a pact test
WHEN I run a pact test
THEN MockService responds with something like BDA52033-F0F1-4EC3-8CA4-E04D71572913

GIVEN I use ExampleGenerator.RandomUUID(.uppercaseDashed) in a pact test
WHEN I run a pact test
THEN MockService responds with something like BDA52033-F0F1-4EC3-8CA4-E04D71572913

GIVEN I use ExampleGenerator.RandomUUID(.dashed) in a pact test
WHEN I run a pact test
THEN MockService responds with something like bda52033-f0f1-4ec3-8ca4-e04d71572913

GIVEN I use ExampleGenerator.RandomUUID(.simple) in a pact test
WHEN I run a pact test
THEN MockService responds with something like bda52033f0f14ec38ca4e04d71572913

🚫 Out of Scope

Bug: .a files don’t seem to be using git-lfs

🌎 Environment

  • Git: 2.28.0
  • git-lfs: 2.11.0 (GitHub; darwin amd64; go 1.14.2)

πŸ’¬ Description

The libpact_mock_server.a files appear to be added directly to the git repo and not git-lfs. Although the .gitattributes file notes that they should be using git-lfs.

🦢 Reproduction Steps

$ git clone https://github.com/surpher/PactSwift
Cloning into 'PactSwift'...
remote: Enumerating objects: 155, done.
remote: Counting objects: 100% (155/155), done.
remote: Compressing objects: 100% (121/121), done.
remote: Total 1720 (delta 81), reused 83 (delta 33), pack-reused 1565
Receiving objects: 100% (1720/1720), 41.60 MiB | 58.20 MiB/s, done.
Resolving deltas: 100% (995/995), done.
Encountered 2 file(s) that should have been pointers, but weren't:
	Resources/iOS/libpact_mock_server.a
	Resources/macOS/libpact_mock_server.a

$ cd PactSwift
# Check if files are LFS pointers, or real files on disk:
$ git show HEAD:Resources/macOS/libpact_mock_server.a
!<arch>
#1/12           0           0     0     0       3514948   `
__.SYMDEF […]

$ git status -sb
## master...origin/master
 M Resources/iOS/libpact_mock_server.a
 M Resources/macOS/libpact_mock_server.a

$ git lfs fetch --all
fetch: 2 object(s) found, done.
fetch: Fetching all references...

$ git status -sb
## master...origin/master
 M Resources/iOS/libpact_mock_server.a
 M Resources/macOS/libpact_mock_server.a

$ git show HEAD:Resources/macOS/libpact_mock_server.a
!<arch>
#1/12           0           0     0     0       3514948   `
__.SYMDEF […]

πŸ€” Expected Results

  • Expected the git clone to work without any warnings.

  • Expected git show HEAD:Resources/macOS/libpact_mock_server.a and git show HEAD:Resources/iOS/libpact_mock_server.a` to both show a pointer file. E.g.

    version https://git-lfs.github.com/spec/v1
    oid sha256:ff65404ef9ef5bad31a5b7fe7469cb24c831c03741046a20b860ba7ec8e47c20
    size 65368
    
  • git status to report no changes after a fresh clone.

😲 Actual Results

  • The .a files appear to have been added to git’s object storage instead of using git-lfs.
  • The PactSwift git clone is always in a β€œdirty” state.

Task: Automate CHANGELOG.md and release process

❕ Problem Statement

Currently no changelog is being generated and "releases" are done manually by setting a tag on a commit. This is tedious, error prone and does not provide any decent transparency on what has been implemented and when unless one goes through the git log. This is fine while PactSwift is still "very beta". As more and more features are being added, defects getting fixed, the change log and release process should be automated as much as possible.

πŸ’¬ Task Description

Prepare a mechanism that would populate the CHANGELOG.md file with changes since last version. This mechanism would also generate a new version tag and append the list of changes.

πŸ‘©β€πŸ”§ Technical Design Notes

  • Create a Release script in ./Scripts
  • Accepts the version number as required argument
  • Updates version number (MARKETING_VERSION) in the ./Configurations/Project-Shared.xcconfig and in Toolbox - consider major, minor or patch number changes
  • Creates a release/tag on the release commit

🀝 Relationships

  • Other Related Issues:

Put Matchers into a namespace

Context

Currently it is very hard to find all available Pact matchers due to inconsistent naming, even though they should provide a decent idea what they do. eg: SomethingLike(_), EachLike(_:min:max:),...

It reads well, but it is hard to find them through auto-suggestions in Xcode:

// DSL
[
  "name": SomethingLike("John"),
  "siblings": EachLike([
    "name": SomethingLike("Jane"),
    "age": IntegerLike(39),
    "gender": RegexLike("female", term: "(male|female)")
  ], min: 0)
]

Suggestion

Namespace Matchers into a class Match and the caller side can then use Match._specific-matcher_.

Define the Match struct as an interface:

public struct Match {

 // List of of Matchers - interface
 public struct SomethingLike { }
 public struct EachLike { }
 public struct Regex { }
 public struct Integer { }
  ...

}

Matchers are implemented in extensions:

public extension SomethingLike: MatchingRuleExpressible {
  // implementation code
}

But this might be an issue if we want to have immutable value types with stored values (for a rule).

Benefits

  1. Although the DSL would be longer to type, yet it would be much easier to find all available matchers using auto-complete features in Xcode.
  2. Reading through the DSL with Match namespace improves understanding of what Pact consumer/provider should expect/do with the value:
// DSL
[
  "name": Match.SomethingLike("John"),
  "siblings": Match.EachLike([
    "name": Match.SomethingLike("Jane"),
    "age": Match.Integer(39),
    "gender": Match.Regex("female", term: "(male|female)")
  ], min: 0)
]

Task: Sanity check the written Pact contract

❕ Problem Statement

libpact_mock_server.a is a dependency brought into PactSwift that stands in place of a mock server when running our consumer Pact tests and is responsible for writing the Pact contract file to disk. This binary is built from Rust codebase. It has happened that we have seen unexpected behaviour when passing data to libpact_mock_server and asking it to write a final Pact file for us. pact_mock_server runs it's own unit tests but it because of the nature of Pact, there may be a lot of cases that are not covered.

It would be good to uncover unexpected behaviour so we can avoid breaking PactSwift's features. And help improve the project.

πŸ’¬ Task Description

  • Write tests/unit tests that generate a Pact file, reads from the generated file and asserts some values"
    -- Description of an interaction(s) is as expected
    -- Contains the expected values for provider state
    -- Interaction contains the expected number of matchers
    -- Interaction contains the expected number of generators

πŸ‘©β€πŸ”§ Technical Design Notes

  • A couple of simple unit tests to cover the important parts of Pact file
  • Read the generated file and run through JSONDecoder()
  • The model to decode into can be tightly coupled to the tests that are written so it's easier to deal with JSONDecoder()
  • Could leverage PactMockServiceTests.testMockService_Writes_PactContract()

🀝 Relationships

  • Other Related Issues:

Task: Create a PactSwift Workshop

❕ Problem Statement

Provide resources to help new users become proficient in Pact and PactSwift

πŸ’¬ Task Description

Create an end-to-end workshop based on the canonical pact one (https://github.com/pact-foundation/pact-workshop-js).

πŸ‘©β€πŸ”§ Technical Design Notes

Suggest using the JS provider as the "backend" for the workshop, to both reduce the effort required in this workshop and because JS is one of the easiest to understand.

🀝 Relationships

See also DiUS/pact-consumer-swift#100

Bug: Only writes one interaction

🌎 Environment

  • Xcode: 12.5
  • Platform: iOS and macOS
  • Version/Release: v0.4.0
  • Dependency manager: SPM and Carthage

πŸ’¬ Description

When running Pact tests, the generated Pact contract only contains one interaction.

🦢 Reproduction Steps

Steps to reproduce the behavior:

  1. Setup a project
  2. Write at least two Pact tests
  3. Run Pact tests

πŸ€” Expected Results

Generated Pact contract contains all the interactions

😲 Actual Results

Generated Pact contract contains only one interaction

🌳 Logs

See Pact-iOS-Spec2-Example project and test run at step:5:67.

πŸ“„ Stack Traces

Include a stack trace if applicable...

🀝 Relationships

  • Related PRs or Issues: #xxx, #yyy

Support macOS architecture

Currently only iOS architecture is supported.

Add support to run Pact tests for macOS platform using Carthage as dependency manager.

Ideally generate a fat framework that supports multiple architectures. This will avoid confusion when importing/using the frameworks on the framework user (eg: linking to /Carthage/build/iOS/, and/or /Carthage/build/mac/ locations, then seeing two frameworks with same name in Xcode project navigator).

Example Generator: Time

Implement an Example Generator for Random Time.

Generates a Time value from the current time either in ISO format or using the provided format string.

Signature:

RandomTime(format: String? = nil)

Example:

`RandomTime(format: String? = nil)`
// generates: 

{ "type": "Time", "format": "HH:mm" }

Null matcher

Overview

Sometimes explicitly matching to null in JSON is required.

Task

Provide a matcher that tells Mock Service to return { ... "key": null ...} and match for Null as value.

Suggested matcher signature:

MatchNull()

Rule declaration:

{ "match": "null" }

Mock server will respond with:

{
  "someKey": null
}

Pact example:

DSL:

.willRespond(
  status: 200,
  body: [
    "some_value": MatchNull()
  ]
)

Example Pact output:

Important bits are { "match":"null" } under matchingRules
and "some_value": null under "body":

{
    "somePactKeys": "somePactValuesOrObjects",
    "interactions": [
        {
            "somePactKeys": "somePactValuesOrObjects",
            "response": {
                "somePactKeys": "somePactValuesOrObjects",
                "status": 200,
                "body": {
                    "some_value": null
                },
                "matchingRules": {
                    "body": {
                        "$.some_value": {
                            "matchers": [
                              { "match": "null" }
                            ]
                        }
                    }
                }
            }
        }
    ]
}

Discussion

Follow pact-specification v3 paragraph describing behaviour for More Specific Type Matchers.

Option to provide own libpact_mock_server.a binaries

πŸ—£ Context

When building the project for the first time it can take an eternity for the project and dependencies to build. This can also happen if the pact-reference/pact-mock-server Submodule changes.

πŸ’¬ Narrative

When I set up a project to use PactSwift
I want to provide my own libpact_mock_server.a binaries
So that I can avoid long build times on development and on CI machines

πŸ“ Notes

One could build their own libpact_mock_server.a and replace/link into the project.
A build argument/flag could be used, and when provided it would skip building the binary in the BuildPhase.
How one plans to provide the binary into the project is up to them, but waiting 25 minutes for the build to finish on GitHub Actions is a bit much.

🎨 Design

βœ… Acceptance Criteria

GIVEN a flag to skip building libpact_mock_server.a is set
WHEN building the project
THEN the command to build mock server dependency is skipped

🚫 Out of Scope

Add port configuration for MockServer

πŸ—£ Context

URL Signing with HMAC not possible with random port.
The signature will be correctly calculated but that means the mock server will have to have a random port as well.
This makes the testing against the Provider a lot more complicated.

πŸ’¬ Narrative

When creating a pact test.
I want to have a configurable base url.
So that when testing the pacts against your BE, you can use a fix address for the mock server.

πŸ“ Notes

If the port is configurable, the entire issue is handled. With the possibility that create_mock_server function fails if the port is already used, but that is something easy to work around.

🎨 Design

MockService should be able to have the port configurable. And pass that configuration when MockServer is created.

Right now the mockServer is created like this mockServer = MockServer().
And sockerAddress and port are private variables of MockServer.

Extend the initializer for MockService to pass the dependencies.
Extend the setup(pact:protocol:completion:) and finalize(pact:completion:) functions to pass the dependencies, or add a initializer for MockServer to pass the dependencies.

βœ… Acceptance Criteria

GIVEN A pact test.
WHEN When running it.
THEN The baseUrl of the mockServer is always the same between test runs.

Support `https`

Apple strongly encourages any network request to be made over https.

In order to avoid fiddling with API Client implementation code to support calling http://localhost in order to Pact test interactions, MockService should be able to run Pact tests by calling Mock Server over https.

Dependency: MockServer lib to be able to start with https

Suggestion

MockService(consumer: "api-consumer", provider: "api-provider", protocol: .secure)

protocol defaults to .standard

SPM support

Context

Developers use different dependency manages. SPM is being touted as the way to go and this framework should be distributed via SPM.

Note

PactSwift is intended to be a framework used with test targets only. Due to SPM limitations it is not yet possible to define a dependency for test target only through Xcode. Therefore if PactSwift is needed, brittle workarounds are possible:

Applies to non-Xcode projects:

  1. Create a Package.swift file where PactSwift is added as a dependency to TestTarget only.
  2. Download libpact_mock_server.a and add into a /lib/ folder in the project /Test/ structure
  3. Run swift build in termial
  4. Run tests in terminal by providing path to the static lib as a linker flag eg: swift test -Xlinker -LPath/To/libpact_mock_server.a

Doing the above for Xcode projects would be wasted effort as Xcode would constantly complain PactSwift module can not be found.

Running Xcode 11.3.x toolchain and running swift test -Xlinker -LPath/To/libpact_mock_server.a returns XCUnwrap unresolved which is a bug in Swift, fixed in 11.4.

Running unit tests in Xcode 11.4 runs everything just fine and pact_mock_server dependency is found.
Running tests in terminal using swift test command fails with ld: warning

Issue

Currently, when running swift test the compiler complains it can not find auto-linked library pact_mock_server:

~/D/p/pact-swift ξ‚° ξ‚  feature/build-pipeline $ ξ‚° PactSwiftServices/Resources ξ‚° swift test
ld: warning: Could not find or use auto-linked library 'pact_mock_server'
Undefined symbols for architecture x86_64:
  "_cleanup_mock_server", referenced from:
      _$s17PactSwiftServices10MockServerC08shutdowndE033_7D8E38AB68B50BAE5CE8F6670397CD50LLyyF in MockServer.swift.o
  "_create_mock_server", referenced from:
      _$s17PactSwiftServices10MockServerC5setup4pact8protocol10completiony10Foundation4DataV_AC16TransferProtocolOys6ResultOySiAA0dE5ErrorOGXEtF in MockServer.swift.o
      _$s17PactSwiftServices10MockServerC8finalize4pact10completiony10Foundation4DataV_ys6ResultOySSAA0dE5ErrorOGcSgtF in MockServer.swift.o
  "_mock_server_matched", referenced from:
      _$s17PactSwiftServices10MockServerC15requestsMatched33_7D8E38AB68B50BAE5CE8F6670397CD50LLSbvg in MockServer.swift.o
  "_mock_server_mismatches", referenced from:
      _$s17PactSwiftServices10MockServerC19mismatchDescription33_7D8E38AB68B50BAE5CE8F6670397CD50LLSSvg in MockServer.swift.o
  "_write_pact_file", referenced from:
      _$s17PactSwiftServices10MockServerC05writeA12ContractFile33_7D8E38AB68B50BAE5CE8F6670397CD50LL10completionyys6ResultOySSAA0dE5ErrorOGXE_tF in MockServer.swift.o
ld: symbol(s) not found for architecture x86_64
[16/17] Linking PactSwiftServicesPackageTests




 ! ξ‚° ~/D/p/pact-swift ξ‚° ξ‚  feature/build-pipeline $ ξ‚° PactSwiftServices/Resources ξ‚° lipo -info libpact_mock_server.a
Architectures in the fat file: libpact_mock_server.a are: x86_64 arm64

Impact

This framework can not be distributed via SPM.

Bug: MockServer build failing after MacOS (Big Sur) update

🌎 Environment

  • Xcode: 12.3
  • Platform: iOS 14.3
  • Version/Release: 0.3.4/0.3.5
  • Dependency manager: SPM

πŸ’¬ Description

I was able to successfully setup PactSwift into my project. After the last MacOS update, the mockserver rust build, which is done as described in the docs through a script build phase, fails.

I updated rust/rustup and cargo-lipo to the newest version, even reinstalled them.

🦢 Reproduction Steps

MacOS 11.1 & build for ios (x86_64-apple-darwin)

Steps to reproduce the behavior:

  1. Run Test build which executes the build-spm-dependency ios script
  2. Observe the error

πŸ€” Expected Results

Successful build of the libpact_mock_server.a file

😲 Actual Results

Error during rust build

🌳 Logs

error: linking with `cc` failed: exit code: 1
[...]
= note: ld: library not found for -lSystem
             clang: error: linker command failed with exit code 1 (use -v to see invocation)

πŸ“„ Stack Traces

N/A

🀝 Relationships

  • Related PRs or Issues: #xxx, #yyy

Update to a matcher doesn't update in the contract

🌎 Environment

  • Xcode: 11.5
  • Platform: iOS
  • Version/Release: master branch
  • Dependency manager: Carthage 0.38

πŸ’¬ Description

When writing a pact test and describing a value in body using an updated matcher, the change to that matcher is not reflected in the pact file.
If the pact file is deleted before running updated again, then the update is reflected in the pact file.

🦢 Reproduction Steps

  1. Setup a pact test environment
  2. Write a pact test where .willRespondWith(body:) contains a matcher:
.willRespondWith(body: [
  "related": EachLike("https://someurl.com/entity/related", min: 0, max: 5)
]
  1. Run this cmd + U
  2. Inspect test file to contain "matchingRules" object for:
...
"body": {
  "$.related": {
    "combine": "AND",
    "matchers": [
       {
           "match": "type",
           "max": 5,
           "min": 0
        }
    ]
  },
...
  1. Update matcher to be EachLike("https://someurl.com/entity/related", min: 1, max: 10)
  2. Run this cmd + U
  3. Inspect test file and observe the min: and max: for the provided matcher

πŸ€” Expected Results

When changing the matcher, the changes should be reflected in the subsequent test runs, eg:

...
"body": {
  "$.related": {
    "combine": "AND",
    "matchers": [
       {
           "match": "type",
           "max": 10,
           "min": 1 
        }
    ]
  },
...

😲 Actual Results

The changes to min: and max: are not recorded and remain set to max: 5 and min: 0

β‘‚ Possible workaround(s)

Before running the tests again with the changes to any of the matchers (tested with changing a SomethingLike() to EqualTo()), remove any existing pact files in order to completely re-generate any and all pact files.

Task: Automate building the `libpact_mock_server.a` binary

❕ Problem Statement

libpact_mock_server.a for iOS and macOS in ./Resources are built from Rust codebase. Rust codebase is under active development and new versions with bug fixes are released on master quite regularly. libpact_mock_server.a should be up to date as much as possible and an automated script would help.

πŸ’¬ Task Description

Create an automated script that builds and replaces the binaries in PactSwift/Resources/#platform#.

πŸ‘©β€πŸ”§ Technical Design Notes

Some helpful resources can be found here https://gist.github.com/surpher/bbf88e191e9d1f01ab2e2bbb85f9b528

🀝 Relationships

  • Other Related Issues: #27, #30

Example Generator: Regex

Implement an Example Generator for Regex.

Generates a random string value from the provided regular expression.

Signature:

Regex(_ regex: String)

Example:

`Regex("\\d{1,8}")`
// generates: 

{ "type": "Regex", "regex": "\\d{1,8}" }

Expose MockService to Objective-C

πŸ—£ Context

When preparing this framework Swift was the only language that was focused on. Since Swift and Objective-C can live together rather nicely it would make sense to expose the PactSwift methods to Objective-C.

πŸ’¬ Narrative

When working on a project that is making network requests
and the project is using Objective-C language
I want to use PactSwift to test my network requests
So that I can increase the confidence in my integration with the remote service(s)

πŸ“ Notes

  • Test it with a demo project written in Objective-C
  • Covering Matchers
  • Cover example generators

βœ… Acceptance Criteria

The following are accessible from Objective-C code:

  • MockService public initialisers

  • MockService public methods

  • MockService.baseUrl

  • MockService.TransferProtocol

  • Interaction public initialisers and methods

  • A failing Pact test displays the xctfail message at the "correct" line in "correct" file.

Task: Find an alternative option for storing big binary files

❕ Problem Statement

Currently the binaries for libpact_mock_server.a built from Rust codebase exceed GitHub's file size 100MB limit. All of the binaries in PactSwift are tracked with git-lfs and stored in GitHub-LFS. Unfortunately GitHub LFS is not free for open source projects and it is charged depending on storage and bandwidth. Each time someone pulls from LFS, the bandwidth gets "eaten" into by about 250MB! And for macOS running tests in CLI, surpher/PactMockServer contains these binaries again due to SPM limitation ons! These costs could potentially explode if any and all CI pipelines continuously pull from PactSwift LFS.

πŸ’¬ Task Description

Research possible workarounds of storing big files/binaries in a secure remote location

πŸ‘©β€πŸ”§ Technical Design Notes

  • Reducing the size of the binaries has already been attempted, but haven't had luck reducing the size.
  • Could we store the binaries in a S3 bucket or similar?,
  • using scripts to fetch the binaries on build if they don't exist?
  • Any and all suggestions to avoid charges for an open source project are welcome.

🀝 Relationships

  • Other Related Issues: #30

XCUI (out-of process) support?

πŸ—£ Context

I’m looking at using Pact for XCUI tests and since this project appears to be the next generation, with Pact v3 support, I was looking to see if I could try it out. However I couldn’t find anything around how this would work with out-of process tests. Like when running XCUI tests. Since the project makes a mention of not using the Ruby Mock Service in the readme I also wonder if this is something that PactSwift doesn’t aim to provide.

So, I’m wondering if it already works, is planned or something that PactSwift doesn’t aim to solve? Thank you!

πŸ’¬ Narrative

When writing UI automation tests using XCUI
I want to be able to use Pact
So that the application under test uses the contracts

βœ… Acceptance Criteria

GIVEN An XCUI test
WHEN There’s a network call
THEN It should use a response from Pact

Task: Cache Rust build artefacts in GitHub actions

❕ Problem Statement

GitHub Actions always run on a clean virtual machine. This means that each job re-builds the Rust binaries in ./Submodules/pact-reference/pact/target which takes a long time. Especially when building for multiple architectures (looking at you iOS device and simulator!). GitHub workflows already have a cache step but it seems that the built artefacts are not cached. Solving this would greatly improve the time the CI takes to run.

The existing configuration seems to have a valid cache, but not the built binaries? (eg: https://github.com/surpher/PactSwift/runs/1324123079?check_suite_focus=true#step:3:1)

Run actions/cache@v2
  with:
    path: Submodules/pact-reference/rust/target
    key: macOS-build-cache-rust-targets-
    restore-keys: macOS-build-cache-rust-targets-
  macOS-build-
  macOS-
  
  env:
    cache-name: cache-rust-targets
Received 22 of 22 (100.0%), 0.0 MBs/sec
Cache Size: ~0 MB (22 B)
/usr/bin/tar --use-compress-program zstd -d -xf /Users/runner/work/_temp/66a965b3-2981-4a4d-9c6c-4722de6fef95/cache.tzst -P -C /Users/runner/work/PactSwift/PactSwift
Cache restored from key: macOS-build-cache-rust-targets-

πŸ’¬ Task Description

Identify why the built artefacts are not cached and re-used in subsequent GitHub Actions jobs and fix it to speed up the CI jobs.

πŸ‘©β€πŸ”§ Technical Design Notes

🀝 Relationships

  • Other Related Issues: #xxx, #yyy

Bug: EachLike matcher ignores any embedded matchers

🌎 Environment

  • Xcode: 12.5
  • Platform: iOS and macOS
  • Version/Release: 0.4.1
  • Dependency manager: SPM, Carthage

πŸ’¬ Description

When embedding any matchers in EachLike, they are ignored and not written to Pact contract.

🦢 Reproduction Steps

Steps to reproduce the behavior:

  1. Write a Pact test
  2. The .willRespondWith(body:...) to include a "example": Matcher.EachLike(SomethingLike("one")) expectation
  3. Run Pact test
  4. Open Pact contract

πŸ€” Expected Results

Pact contract should contain

"$.body.example": {
  "min": 1
},
"$.body.example[*].*": {
  "match": "type"
},

😲 Actual Results

The matcher block for SomethingLike is missing

🌳 Logs

Include any logs or command output if applicable...

πŸ“„ Stack Traces

Include a stack trace if applicable...

🀝 Relationships

  • Related PRs or Issues: #xxx, #yyy

Task: Example of using PactSwift with Xcode's SPM

❕ Problem Statement

PactSwift is intended to be used with test targets only. For obvious reasons, but among them, it's a tool to help us test our networking layer, it contains "huge" binaries, etc. It appears that Xcode 11 or Xcode 12 SPM interface doesn't allow us to define to what targets the package bundles into.

πŸ’¬ Task Description

Research how PactSwift could be used with SPM in an iOS project directly in Xcode instead of faffing about in CLI.
PactSwift should only be available to be imported in test targets.

  • PactSwift can already be successfully pulled into an Xcode project as a Swift Package and be imported into test target
  • Requires a manual step defining "Library Search Path" in build settings
  • Requires replacing fake binaries with binaries build on the user's machine

πŸ‘©β€πŸ”§ Technical Design Notes

  • script to guide the user to compile the binary for the platform they need, it should be possible to re-use it in CI/CD pipeline!
  • demo project with a working Pact test
  • Instructions on how to set up with SPM

🀝 Relationships

  • Other Related Issues: #xxx, #yyy

Example Generator: RandomInt

Implement an Example Generator for Random Integer.

Signature:

RandomInt(min: Int = 0, max: Int? = 2147483647)

Example:

`RandomInt(min: 0)`
// generates: 

{ "type": "RandomInt", "min": 0, "max": 2147483647 }

Support matchers in response headers and request body, headers, query

πŸ—£ Context

Currently Matchers are only supported in Response body. Matchers should also be available to set for other elements of requests and responses.

πŸ’¬ Narrative

When preparing a Pact test
I want to use matchers in response header. request bodies, request headers and request query
So that I can write a better Pact test that can also handle random data.

πŸ“ Notes

🎨 Design

βœ… Acceptance Criteria

GIVEN a withResponse setup of the pact test
WHEN entering header values
THEN I can use any available Matcher

GIVEN a withRequest setup of the pact test
WHEN entering header values
THEN I can use any available Matcher

GIVEN a withRequest setup of the pact test
WHEN entering query values
THEN I can use any available Matcher

🚫 Out of Scope

Example Generator: RandomString

Implement an Example Generator for Random String.

Generates a random hexadecimal value (String) with the provided number of digits.

Signature:

RandomString(size: Int? = 20)

Example:

`RandomString(size: 16)`
// generates: 

{ "type": "RandomString", "digits": 16 }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.