提供者状态

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

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

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