⚠️ NOTE⚠️
pact-swift is under heavy development and not all features are complete. Not everything is documented properly.
This framework provides a Swift DSL for generating Pact contracts.
Implements Pact Specification v3.
The one major advantage of this framework over pact-consumer-swift is that it does not depend on Ruby Mock Service to be running on your machine (or on CI/CD agent). Also, it does not require you to fiddle with test pre-actions and post-actions.
github "surpher/PactSwift" ~> 0.1carthage update --platform ios --no-use-binariesAdd PactSwift as a dependency to your test target in Package.swift:
...
dependencies: [
.package(url: "https://github.com/surpher/PactSwift.git", .branch("master"))
],
...Run tests in terminal by providing path to static lib as a linker flag:
swift test -Xlinker -LRelativePathTo/libFolder
PactSwift through SPM requires you to link a libpact_mock_server.a for the appropriate architecture. You can find them in /Resources/ folder.
You can compile a custom lib from pact-reference/rust codebase.
We're actively looking for an alternative approach to using static libs with SPM!
NOTE: This framework is intended to be used in your test target. Do not embed it into your app bundle!
In your test targets build settings, update Framework Search Paths configuration to include $(PROJECT_DIR)/Carthage/Build/iOS (non-recursive):
In your test targets build settings, update Runpath Search Paths configuration to include $(FRAMEWORK_SEARCH_PATHS):
Edit your scheme and add PACT_DIR environment variable (Run step) with path to the directory you want your Pact contracts to be written to. By default, Pact contracts are written to /tmp/pacts.
Documents folder in the sandbox (eg: ~/Library/Containers/com.example.your-project-name/Data/Documents) and can not be overriden by the environment variable PACT_DIR.
- Instantiate a
MockServiceobject by defining pacticipants, - Define the state of the provider for an interaction (one Pact test),
- Define the expected
requestfor the interaction, - Define the expected
responsefor the interaction, - Run the test by making the API request using your API client and assert what you need asserted,
- Share the generated Pact contract file with your provider (eg: upload to a Pact Broker),
- Run
can-i-deploy(eg: on your CI/CD) to deploy with confidence.
import XCTest
import PactSwift
@testable import ExampleProject
class PassingTestsExample: XCTestCase {
var mockService = MockService(consumer: "Example-iOS-app", provider: "users-service")
// MARK: - Tests
func testGetUsers() {
// #1 - Define the API contract by configuring how `mockService`, and consequently the "real" API, will behave for this specific API request we are testing here
_ = mockService
// #2 - Define the interaction description and provider state for this specific API request that we are testing
.uponReceiving("A request for a list of users")
.given(ProviderState(description: "users exist", params: ["first_name": "John", "last_name": "Tester"])
// #3 - Define the request we promise our API consumer will make
.withRequest(
method: .GET,
path: "/api/users",
headers: nil, // `nil` means we (and the API Provider) should not care about headers. If there are values there, fine, we're just not _demanding_ anything.
body: nil // same as with headers
)
// #4 - Define what we expect `mockService`, and consequently the "real" API, to respond with for this particular API request we are testing
.willRespondWith(
status: 200,
headers: nil, // `nil` means we don't care what the headers returned from the API are. If there are values in the header, fine, we're just not _demanding_ anything in the header.
body: [
"page": SomethingLike(1), // We will use matchers here, as we normally care about the types and structure, not necessarily the actual value.
"per_page": SomethingLike(20),
"total": SomethingLike(58),
"total_pages": SomethingLike(3),
"data": EachLike(
[
"id": IntegerLike(1),
"first_name": SomethingLike("John"),
"last_name": SomethingLike("Tester"),
"salary": DecimalLike(125000.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
mockService.run(waitFor: 1) { [unowned self] completed in
// #6 - _Redirect_ your API calls to the address MockService runs on - replace base URL, but path should be the same
apiClient.baseUrl = self.mockService.baseUrl
// #7 - Make the API request.
apiClient.getUsers() { users in
// #8 - Test that the 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 - Notify MockService we're done with our test, else your Pact test will time out.
completed()
}
}
}
// More tests for other interactions and/or provider states...
func testGetUsers_Unauthorised() {
// ... code
}
// etc.
}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/.
If you set the PACT_DIR environment variable, your Xcode setup is correct and your tests successfully run, then you should see the generated Pact files in:
$(PACT_DIR)/_consumer_name_-_provider_name_.json.
Publish your generated Pact file(s) to your Pact Broker or a hosted service, so that your API-provider team can always retrieve them from one location, even when pacts change.
See how you can use 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.
See pact-swift-examples repo.
See CODE_OF_CONDUCT.md
See CONTRIBUTING.md
This project takes ideas from pact-consumer-swift and pull request Feature/native wrapper PR.


