Pact Consumer Swift

Build Codecov Carthage compatible Swift Package Manager Swift Badge w/ CocoaPod Version Badge w/ Supported Platforms License: MIT Twitter


一个新版本正在积极开发中,它具有 Pact Specification v3、简化的安装方式以及更好的模拟服务器进程管理,可以在 PactSwift 中找到。我们目前正在寻找人员试用并提供反馈。

该库提供了一个 Swift / Objective C DSL,用于创建 Consumer Pacts。它为依赖系统之间基于 HTTP 的集成(或某些实现的 message queues)提供 Consumer Driven Contract Testing(消费者驱动的契约测试) 支持。

但为什么要用它呢? 为了测试你的应用程序和服务之间的通信边界。你可以在这里观看关于 Pact 如何在移动环境中工作的演示:Yow! Connected 2016 Andrew Spinks - Increasing The Confidence In Your Service Integrations

实现了 Pact Specification v2,包括 flexible matching(灵活匹配)

此 DSL 依赖于 Ruby pact-ruby-standalone (brew tap) 来为测试提供模拟服务。

安装

注意:有关从 0.2 升级到 0.3 的说明,请参阅 Upgrading(升级)

安装 Pact 模拟服务

Homebrew

brew tap pact-foundation/pact-ruby-standalone
brew install pact-ruby-standalone

这将安装以下工具

pact
pact-broker
pact-message
pact-mock-service
pact-provider-verifier
pact-publish
pact-stub-service

手动安装

或者,你可以下载并安装适用于你的平台的 pact-ruby-standalone 档案,并按照 Pact Ruby Standalone release notes(Pact Ruby 独立版本发布说明) 中编写的安装说明进行安装。

Xcode 设置

在 Xcode 中,编辑你的 scheme,并在 Test 中添加pre-post-actions来启动和停止 pact-mock-service。确保在下拉菜单Provide build settings from中选择你的 target。

# Pre-actions
PATH=/path/to/your/standalone/pact/bin:$PATH
pact-mock-service start --pact-specification-version 2.0.0 --log "${SRCROOT}/tmp/pact.log" --pact-dir "${SRCROOT}/tmp/pacts" -p 1234

# Post-actions
PATH=/path/to/your/standalone/pact/bin:$PATH
pact-mock-service stop

注意:你生成的 Pact 文件将被放入 "${SRCROOT}/tmp/pacts" 文件夹中。

Xcode Scheme Test Pre-actions

将 PactConsumerSwift 库添加到你的项目

使用 Carthage

使用 CocoaPods

使用 Swift Package Manager

编写 Pact 测试

使用 Swift 进行测试

编写一个类似于以下的单元测试(注意:此示例使用 Quick 测试框架)

import PactConsumerSwift

...
  beforeEach {
    animalMockService = MockService(provider: "Animal Service", consumer: "Animal Consumer Swift")
    animalServiceClient = AnimalServiceClient(baseUrl: animalMockService!.baseUrl)
  }

  it("gets an alligator") {
    animalMockService!.given("an alligator exists")
                      .uponReceiving("a request for an alligator")
                      .withRequest(method:.GET, path: "/alligator")
                      .willRespondWith(status:200,
                                       headers: ["Content-Type": "application/json"],
                                       body: ["name": "Mary"])

    //Run the tests
    animalMockService!.run { (testComplete) -> Void in
      animalServiceClient!.getAlligator { (alligator) in
        expect(alligator.name).to(equal("Mary"))
        testComplete()
      }
    }
  }

可以在 run 函数中包含一个可选的 timeout (秒) 参数。 默认值为 30 秒。

...
    animalMockService!.run(timeout: 60) { (testComplete) -> Void in
      animalServiceClient!.getAlligator { (alligator) in
        expect(alligator.name).to(equal("Mary"))
        testComplete()
      }
    }

使用 Objective-C 进行测试

编写一个类似于以下的单元测试

@import PactConsumerSwift;
...
- (void)setUp {
  [super setUp];
  self.animalMockService = [[MockService alloc] initWithProvider:@"Animal Provider"
                                                        consumer:@"Animal Service Client Objective-C"];
  self.animalServiceClient = [[OCAnimalServiceClient alloc] initWithBaseUrl:self.animalMockService.baseUrl];
}

- (void)testGetAlligator {
  typedef void (^CompleteBlock)();

  [[[[self.animalMockService given:@"an alligator exists"]
                             uponReceiving:@"oc a request for an alligator"]
                             withRequestHTTPMethod:PactHTTPMethodGET
                                              path:@"/alligator"
                                             query:nil headers:nil body:nil]
                             willRespondWithHTTPStatus:200
                                               headers:@{@"Content-Type": @"application/json"}
                                                  body: @"{ \"name\": \"Mary\"}" ];

  [self.animalMockService run:^(CompleteBlock testComplete) {
      Animal *animal = [self.animalServiceClient getAlligator];
      XCTAssertEqualObjects(animal.name, @"Mary");
      testComplete();
  }];
}

可以在 run 函数中包含一个可选的 timeout (秒) 参数。 默认值为 30 秒。

...
  [self.animalMockService run:^(CompleteBlock testComplete) {
      Animal *animal = [self.animalServiceClient getAlligator];
      XCTAssertEqualObjects(animal.name, @"Mary");
      testComplete();
  } timeout:60];
}

使用 XCTest 进行测试

编写一个类似于以下的单元测试

import PactConsumerSwift
...
  var animalMockService: MockService?
  var animalServiceClient: AnimalServiceClient?

  override func setUp() {
    super.setUp()

    animalMockService = MockService(provider: "Animal Provider", consumer: "Animal Service Client")
    animalServiceClient = AnimalServiceClient(baseUrl: animalMockService!.baseUrl)
  }

  func testItGetsAlligator() {
    // Prepare the expecated behaviour using pact's MockService
    animalMockService!
      .given("an alligator exists")
      .uponReceiving("a request for alligator")
      .withRequest(method: .GET, path: "/alligator")
      .willRespondWith(status: 200,
                       headers: ["Content-Type": "application/json"],
                       body: [ "name": "Mary" ])

    // Run the test
    animalMockService!.run(timeout: 60) { (testComplete) -> Void in
      self.animalServiceClient!.getAlligator { (response) -> in
        XCTAssertEqual(response.name, "Mary")
        testComplete()
      }
    }
  }
  ...

可以在 run 函数中包含一个可选的 timeout (秒) 参数。 默认值为 30 秒。

...
    // Run the test
    animalMockService!.run(timeout: 60) { (testComplete) -> Void in
      self.animalServiceClient!.getAlligator { (response) -> in
        XCTAssertEqual(response.name, "Mary")
        testComplete()
      }
    }

有关如何通过 https 进行测试的示例,请参见 PactSSLSpec.swift

匹配

除了逐字值匹配之外,Matcher 类中还有 3 个有用的匹配函数,可以提高表达性并减少脆弱的测试用例。

注意:需要注意的一点是,你需要使用有效的 Ruby regular expressions(正则表达式) 并转义双反斜杠。

有关如何期望错误响应、如何使用查询参数以及 Matchers 的示例,请参见 PactSpecs.swiftPactObjectiveCTests.m

有关请求/响应匹配的更多信息,请参见 [Matching][getting_started/matching]。

在你的 CI 中使用

Xcode 的pre-actionspost-actions不遵守非零脚本退出,因此如果发布到 Pact Broker 失败,将不会使你的构建失败。 如果你想将 Pact 文件作为 CI 的一部分上传到 Pact Broker,我们建议你在你的 CI 工作流程中创建一个单独的步骤来负责这项任务。

有关安装说明以及如何使用 pact-broker 客户端,请参见 pact-ruby-standalone 页面。

针对你正在集成的服务验证你的客户端

如果你的设置正确并且你的测试针对 pack 模拟服务器运行,那么你应该在这里看到一个日志文件:$YOUR_PROJECT/tmp/pact.log 并在以下位置生成 pact:$YOUR_PROJECT/tmp/pacts/...

发布你生成的 pact 文件到你的 Pact BrokerHosted Pact Broker(托管的 Pact Broker),以便你的API 提供者可以始终从一个位置检索它们,即使 pact 发生更改也是如此。 或者只是简单地将 pact 文件发送给你的 API 提供者开发人员,以便他们可以在 API 响应的测试中使用它们。 有关更多信息,请参见 Verifying pacts(验证 pact)。 有关带有 ruby 后端服务的端到端示例,请查看 KatKit example(KatKit 示例)

另外,请查看这篇关于 using a dockerized Node.js service(使用 dockerized Node.js 服务) 的文章,该服务使用 provider states。

更多阅读

贡献

请阅读 CONTRIBUTING.md