- 提供者状态
- 消费者代码库
- 提供者代码库
- 基状态
- 全局状态
- 测试错误响应
- 在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, …}
* 代表提供者状态
** 代表请求描述
消费者代码库
举个例子,消费者项目中用于创建一段契约的代码可能看起来像这样:
describe MyServiceProviderClient dosubject { MyServiceProviderClient.new }describe "get_something" docontext "when a thing exists" dobefore domy_service.given("a thing exists").upon_receiving("a request for a thing").with(method: 'get', path: '/thing').will_respond_with(status: 200,headers: { 'Content-Type' => 'application/json' },body: { name: 'A small something'} )endit "returns a thing" doexpect(subject.get_something).to eq(SomethingModel.new('A small something'))endendcontext "when a thing does not exist" dobefore domy_service.given("a thing does not exist").upon_receiving("a request for a thing").with(method: 'get', path: '/thing').will_respond_with(status: 404)endit "returns nil" doexpect(subject.get_something).to be_nilendendendend
提供者代码库
根据上述的提供者状态,来定义能够创建适当数据的服务提供者状态,可以在服务提供者项目中这样来写。(此处的消费者名称必须与消费者项目中配置的消费者名称相符,才能正确地找到这些提供者状态。)
# In /spec/service_consumers/provider_states_for_my_service_consumer.rbPact.provider_states_for 'My Service Consumer' doprovider_state "a thing exists" doset_up do# Create a thing here using your framework of choice# eg. Sequel.sqlite[:somethings].insert(name: "A small something")endtear_down do# Any tear down steps to clean up your codeendendprovider_state "a thing does not exist" dono_op # If there's nothing to do because the state name is more for documentation purposes,# you can use no_op to imply this.endend
在pact_helper.rb中引入上面所写的提供者状态文件
# In /spec/service_consumers/pact_helper.rbrequire './spec/service_consumers/provider_states_for_my_service_consumer.rb'
基状态
对于给定消费者来定义一些能够在每次交互前/后都会运行的代码,而不管是否指定了提供者状态,只需将set_up/tear_down代码块放置于provider_state之外即可。
Pact.provider_states_for 'My Service Consumer' doset_up do# This will run before the set_up for provider state specified for the interaction.# eg. create API user, set the expected basic auth detailsendtear_down do# ...# This will run after the tear_down for the specified provider state.endend
全局状态
全局状态会先于消费者定义的基状态之前被设置。避免使用全局设置来创建数据,因为当存在多个消费者时这会使测试变得脆弱。
Pact.set_up do# eg. start database cleaner transactionendPact.tear_down do# eg. clean databaseend
测试错误响应
测试客户端如何处理错误的响应是非常重要的。
# Consumer codebasedescribe MyServiceProviderClient dosubject { MyServiceProviderClient.new }describe "get_something" docontext "when an error occurs retrieving a thing" dobefore domy_service.given("an error occurs while retrieving a thing").upon_receiving("a request for a thing").with(method: 'get', path: '/thing').will_respond_with(status: 500,headers: { 'Content-Type' => 'application/json' },body: { message: "An error occurred!" } )endit "raises an error" doexpect{ subject.get_something }.to raise_error /An error occurred!/endendendend
# Provider codebasePact.provider_states_for 'My Service Consumer' doprovider_state "an error occurs while retrieving a thing" doset_up do# Stubbing is ususally the easiest way to generate an error with predictable error text.allow(ThingRepository).to receive(:find).and_raise("An error occurred!")endendend
在set_up和tear_down中使用引入的模块
按照这种方法引入的任意模块在set_up与tear_down代码块中都是可以使用的。 这样做的一个常见用途是将用于设置数据的工厂方法包含进来,这样提供者状态文件将不会过于臃肿。
Pact.configure do | config |config.include MyTestHelperMethodsend
