• 提供者状态
    • 消费者代码库
    • 提供者代码库
    • 基状态
    • 全局状态
    • 测试错误响应
    • 在set_up和tear_down中使用引入的模块

    提供者状态

    关于这一高级话题,请先阅读提供者状态一节的介绍。

    当按照以下形式来阅读时,提供者状态中的文本应该具有足够的可读性(自动生成的文档是按这样的形式展示的):

    Given an alligator with the name Mary exists *

    Upon receiving a request to retrieve an alligator by name ** from Some Consumer

    With {“method” : “get”, “path” : “/alligators/Mary” }

    Some Provider will respond with { “status” : 200, …}

    * 代表提供者状态

    ** 代表请求描述

    消费者代码库

    举个例子,消费者项目中用于创建一段契约的代码可能看起来像这样:

    1. describe MyServiceProviderClient do
    2. subject { MyServiceProviderClient.new }
    3. describe "get_something" do
    4. context "when a thing exists" do
    5. before do
    6. my_service.given("a thing exists").
    7. upon_receiving("a request for a thing").with(method: 'get', path: '/thing').
    8. will_respond_with(status: 200,
    9. headers: { 'Content-Type' => 'application/json' },
    10. body: { name: 'A small something'} )
    11. end
    12. it "returns a thing" do
    13. expect(subject.get_something).to eq(SomethingModel.new('A small something'))
    14. end
    15. end
    16. context "when a thing does not exist" do
    17. before do
    18. my_service.given("a thing does not exist").
    19. upon_receiving("a request for a thing").with(method: 'get', path: '/thing').
    20. will_respond_with(status: 404)
    21. end
    22. it "returns nil" do
    23. expect(subject.get_something).to be_nil
    24. end
    25. end
    26. end
    27. end

    提供者代码库

    根据上述的提供者状态,来定义能够创建适当数据的服务提供者状态,可以在服务提供者项目中这样来写。(此处的消费者名称必须与消费者项目中配置的消费者名称相符,才能正确地找到这些提供者状态。)

    1. # In /spec/service_consumers/provider_states_for_my_service_consumer.rb
    2. Pact.provider_states_for 'My Service Consumer' do
    3. provider_state "a thing exists" do
    4. set_up do
    5. # Create a thing here using your framework of choice
    6. # eg. Sequel.sqlite[:somethings].insert(name: "A small something")
    7. end
    8. tear_down do
    9. # Any tear down steps to clean up your code
    10. end
    11. end
    12. provider_state "a thing does not exist" do
    13. no_op # If there's nothing to do because the state name is more for documentation purposes,
    14. # you can use no_op to imply this.
    15. end
    16. end

    在pact_helper.rb中引入上面所写的提供者状态文件

    1. # In /spec/service_consumers/pact_helper.rb
    2. require './spec/service_consumers/provider_states_for_my_service_consumer.rb'

    基状态

    对于给定消费者来定义一些能够在每次交互前/后都会运行的代码,而不管是否指定了提供者状态,只需将set_up/tear_down代码块放置于provider_state之外即可。

    1. Pact.provider_states_for 'My Service Consumer' do
    2. set_up do
    3. # This will run before the set_up for provider state specified for the interaction.
    4. # eg. create API user, set the expected basic auth details
    5. end
    6. tear_down do
    7. # ...
    8. # This will run after the tear_down for the specified provider state.
    9. end
    10. end

    全局状态

    全局状态会先于消费者定义的基状态之前被设置。避免使用全局设置来创建数据,因为当存在多个消费者时这会使测试变得脆弱。

    1. Pact.set_up do
    2. # eg. start database cleaner transaction
    3. end
    4. Pact.tear_down do
    5. # eg. clean database
    6. end

    测试错误响应

    测试客户端如何处理错误的响应是非常重要的。

    1. # Consumer codebase
    2. describe MyServiceProviderClient do
    3. subject { MyServiceProviderClient.new }
    4. describe "get_something" do
    5. context "when an error occurs retrieving a thing" do
    6. before do
    7. my_service.given("an error occurs while retrieving a thing").
    8. upon_receiving("a request for a thing").with(method: 'get', path: '/thing').
    9. will_respond_with(
    10. status: 500,
    11. headers: { 'Content-Type' => 'application/json' },
    12. body: { message: "An error occurred!" } )
    13. end
    14. it "raises an error" do
    15. expect{ subject.get_something }.to raise_error /An error occurred!/
    16. end
    17. end
    18. end
    19. end
    1. # Provider codebase
    2. Pact.provider_states_for 'My Service Consumer' do
    3. provider_state "an error occurs while retrieving a thing" do
    4. set_up do
    5. # Stubbing is ususally the easiest way to generate an error with predictable error text.
    6. allow(ThingRepository).to receive(:find).and_raise("An error occurred!")
    7. end
    8. end
    9. end

    在set_up和tear_down中使用引入的模块

    按照这种方法引入的任意模块在set_up与tear_down代码块中都是可以使用的。 这样做的一个常见用途是将用于设置数据的工厂方法包含进来,这样提供者状态文件将不会过于臃肿。

    1. Pact.configure do | config |
    2. config.include MyTestHelperMethods
    3. end