-
Notifications
You must be signed in to change notification settings - Fork 216
Regular expressions and type matching with Pact
Version 2 matching has been released, see docs here
Sometimes you will have keys in a request or response with values that are hard to know beforehand - timestamps and generated IDs are two examples.
What you need is a way to say "I expect something matching this regular expression, but I don't care what the actual value is".
animal_service.given("an alligator named Mary exists").
upon_receiving("a request for an alligator").
with(
method: "get",
path: "/alligators/Mary",
headers: {"Accept" => "application/json"}).
will_respond_with(
status: 200,
headers: {"Content-Type" => "application/json"},
body: {
name: "Mary",
dateOfBirth: Pact.term(
generate: "02/11/2013",
matcher: /\d{2}\/\d{2}\/\d{4}/)
})
Note the use of the Pact::Term
. When you run the Consumer tests, the mock server will return the value that you specified to "generate", and when you verify the pact in the Provider codebase, it will ensure that the value at that key matches the specified regular expression.
You can also use Pact::Term
for request matching.
animal_service.given("an alligator named Mary exists").
upon_receiving("a request for an alligator").
with(
method: "get",
path: "/alligators/Mary",
query: Pact.term(
generate: "transactionId=1234",
matcher: /transactionId=\d{4}/),
will_respond_with(
status: 200, ...)
The matcher
will be used to ensure that the actual request query was in the right format, and the value specified in the generate
field will be the one that is replayed against the provider as an example of what a real value would look like. This means that your provider states can still use known values to set up their data, but your Consumer tests can generate data on the fly.
You can use Pact::Term
for request and response header values, the request query, and inside request and response bodies. Note that regular expressions can only be used on Strings. Furthermore, request queries, when specified as strings are just matched as normal String - no flexible ordering of parameters is catered for. For flexible ordering, specify it as a Hash, which in turn may include Pact::Terms
Often, you will not care what the exact value is at a particular path is, you just care that a value is present and that it is of the expected type. For this scenario, you can use Pact::SomethingLike
(this library is already included in require 'pact/consumer/rspec'
).
animal_service.given("an alligator named Mary exists").
upon_receiving("a request for an alligator").
with(
method: "get",
path: "/alligators/Mary",
headers: {"Accept" => "application/json"}).
will_respond_with(
status: 200,
headers: {"Content-Type" => "application/json"},
body: Pact.like(
name: "Mary",
age: 73)
)
The mock server will return {"name": "Mary", "age": 73}
in the consumer tests, but when pact:verify
is run in the provider, it will just check that the type of the name
value is a String, and that the type of the age
value is a Fixnum. If you wanted an exact match on "Mary", but to allow any age, you would only wrap the 73
in the Pact::SomethingLike
. For example:
body: {
name: "Mary",
age: Pact.like(73)
}
For request matching, the mock server will allow any values of the same type to be used in the consumer test, but will replay the given values in pact:verify
.
animal_service.given("an alligator named Mary exists").
upon_receiving("a request to update an alligator").
with(
method: "put",
path: "/alligators/Mary",
headers: {"Accept" => "application/json"},
body: {
age: Pact.like(10)
}).
will_respond_with(
status: 200,
headers: {"Content-Type" => "application/json"},
body: {
age: 10
})
Query params can be specified as a string or a hash. When specified as a string, an exact match will be performed. You may use a Pact::Term, but only over the query string as a whole. Note that the query params must already be URL encoded in the expectation. (This will change for v2 matching.)
animal_service.given("some alligators exist").
upon_receiving("a request to search for alligators").
with(
method: "get",
path: "/alligators",
query: "name=Mary+Jane&age=8")
...
Alternatively, if the order of the query parameters does not matter, you can specify the query as a hash. You can embed Pact::Terms or Pact::SomethingLike inside the hash. Remember that all query params will be parsed to strings, so don't use a SomethingLike with a number.
animal_service.given("some alligators exist").
upon_receiving("a request to search for alligators").
with(
method: "get",
path: "/alligators",
query: {
# No need to encode params in the hash
name: 'Mary Jane',
age: '8',
# Specify a param with multiple values using an
# array - order will be enforced
children: ['Betty', 'Sue']
})
...