Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generalise request matching in PublishingApi assertions
The assertions provided by the PublishingApi test helpers are useful. The existing implementation allows an assertion to require that a request was made to a publishing api endpoint with a hash containing a certain list of required attributes. However the underlying matching process would only take into account the outermost level of the hash when performing the comparison. This means that you could make an flexible assertion about the required elements at the top level of a document sent to the publishing api, but if you wanted to make assertions about the details section or elements nested within the details section, you could only perform an exact match on the details hash. This would lead to brittle, difficult to maintain tests. This commit allows passing custom matcher predicates to the assertions and defines a new matcher which can match nested structures more loosely. The original strict matching behaviour is retained as a default to avoid possible unintended side-effects on the test suites of other apps. We considered making the looser behaviour the default, but after reviewing the usage of some other apps I had enough doubt about that I thought it would be better to be conservative and avoid changing the default matching behaviour. To use the assertions with the looser matcher you can do the following: ``` assert_publishing_api_put_item( base_path, request_json_matching(details: {title: "My title"}) ) ``` Note that both matchers will convert symbols to strings so the hash can use either symbol or string keys.
- Loading branch information
Showing
2 changed files
with
208 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
require 'test_helper' | ||
require 'gds_api/publishing_api' | ||
require 'gds_api/test_helpers/publishing_api' | ||
|
||
describe GdsApi::TestHelpers::PublishingApi do | ||
include GdsApi::TestHelpers::PublishingApi | ||
let(:base_api_url) { Plek.current.find("publishing-api") } | ||
let(:publishing_api) { GdsApi::PublishingApi.new(base_api_url) } | ||
|
||
describe "#assert_publishing_api_put_item" do | ||
before { stub_default_publishing_api_put } | ||
let(:base_path) { "/example" } | ||
|
||
it "matches a put request with any empty attributes by default" do | ||
publishing_api.put_content_item(base_path, {}) | ||
assert_publishing_api_put_item(base_path) | ||
end | ||
|
||
it "matches a put request with any arbitrary attributes by default" do | ||
random_attributes = Hash[10.times.map {|n| [Random.rand * 1_000_000_000_000, Random.rand * 1_000_000_000_000]}] | ||
publishing_api.put_content_item(base_path, random_attributes) | ||
assert_publishing_api_put_item(base_path) | ||
end | ||
|
||
it "if attributes are specified, matches a request with at least those attributes" do | ||
publishing_api.put_content_item(base_path, {"required_attribute" => 1, "extra_attrbibute" => 1}) | ||
assert_publishing_api_put_item(base_path, {"required_attribute" => 1}) | ||
end | ||
|
||
it "matches using a custom request matcher" do | ||
publishing_api.put_content_item(base_path, {}) | ||
matcher_was_called = false | ||
matcher = ->(request) { matcher_was_called = true; true } | ||
assert_publishing_api_put_item(base_path, matcher) | ||
assert matcher_was_called, "matcher should have been called" | ||
end | ||
end | ||
|
||
describe '#request_json_strictly_matching predicate' do | ||
describe "nested required attribute" do | ||
let(:matcher) { request_json_matching({"a" => {"b" => 1}}) } | ||
|
||
it "matches a body with exact same nested hash strucure" do | ||
assert matcher.call(stub("request", body: '{"a": {"b": 1}}')) | ||
end | ||
|
||
it "matches a body with exact same nested hash strucure and an extra attribute at the top level" do | ||
assert matcher.call(stub("request", body: '{"a": {"b": 1}, "c": 3}')) | ||
end | ||
|
||
it "does not match a body where the inner hash has the required attribute and an extra one" do | ||
assert matcher.call(stub("request", body: '{"a": {"b": 1, "c": 2}}')) | ||
end | ||
|
||
it "does not match a body where the inner hash has the required attribute with the wrong value" do | ||
refute matcher.call(stub("request", body: '{"a": {"b": 0}}')) | ||
end | ||
|
||
it "does not match a body where the inner hash lacks the required attribute" do | ||
refute matcher.call(stub("request", body: '{"a": {"c": 1}}')) | ||
end | ||
end | ||
|
||
describe "hash to match uses symbol keys" do | ||
let(:matcher) { request_json_matching({a: 1}) } | ||
|
||
it "matches a json body" do | ||
assert matcher.call(stub("request", body: '{"a": 1}')) | ||
end | ||
end | ||
end | ||
|
||
describe '#request_json_matching predicate' do | ||
describe "no required attributes" do | ||
let(:matcher) { request_json_matching({}) } | ||
|
||
it "matches an empty body" do | ||
assert matcher.call(stub("request", body: "{}")) | ||
end | ||
|
||
it "matches a body with some attributes" do | ||
assert matcher.call(stub("request", body: '{"a": 1}')) | ||
end | ||
end | ||
|
||
describe "one required attribute" do | ||
let(:matcher) { request_json_matching({"a" => 1}) } | ||
|
||
it "does not match an empty body" do | ||
refute matcher.call(stub("request", body: "{}")) | ||
end | ||
|
||
it "does not match a body with the required attribute if the value is different" do | ||
refute matcher.call(stub("request", body: '{"a": 2}')) | ||
end | ||
|
||
it "matches a body with the required attribute and value" do | ||
assert matcher.call(stub("request", body: '{"a": 1}')) | ||
end | ||
|
||
it "matches a body with the required attribute and value and extra attributes" do | ||
assert matcher.call(stub("request", body: '{"a": 1, "b": 2}')) | ||
end | ||
end | ||
|
||
describe "nested required attribute" do | ||
let(:matcher) { request_json_matching({"a" => {"b" => 1}}) } | ||
|
||
it "matches a body with exact same nested hash strucure" do | ||
assert matcher.call(stub("request", body: '{"a": {"b": 1}}')) | ||
end | ||
|
||
it "matches a body where the inner hash has the required attribute and an extra one" do | ||
assert matcher.call(stub("request", body: '{"a": {"b": 1, "c": 2}}')) | ||
end | ||
|
||
it "does not match a body where the inner hash has the required attribute with the wrong value" do | ||
refute matcher.call(stub("request", body: '{"a": {"b": 0}}')) | ||
end | ||
|
||
it "does not match a body where the inner hash lacks the required attribute" do | ||
refute matcher.call(stub("request", body: '{"a": {"c": 1}}')) | ||
end | ||
end | ||
|
||
describe "hash to match uses symbol keys" do | ||
let(:matcher) { request_json_matching({a: {b: 1}}) } | ||
|
||
it "matches a json body" do | ||
assert matcher.call(stub("request", body: '{"a": {"b": 1}}')) | ||
end | ||
end | ||
|
||
describe "nested arrays" do | ||
let(:matcher) { request_json_matching({"a" => [1]}) } | ||
|
||
it "matches a body with exact same inner array" do | ||
assert matcher.call(stub("request", body: '{"a": [1]}')) | ||
end | ||
|
||
it "does not match a body with an array with extra elements" do | ||
refute matcher.call(stub("request", body: '{"a": [1, 2]}')) | ||
end | ||
end | ||
|
||
describe "hashes in nested arrays" do | ||
let(:matcher) { request_json_matching({"a" => [{"b" => 1}, 2]}) } | ||
|
||
it "matches a body with exact same inner array" do | ||
assert matcher.call(stub("request", body: '{"a": [{"b": 1}, 2]}')) | ||
end | ||
|
||
it "matches a body with an inner hash with extra elements" do | ||
assert matcher.call(stub("request", body: '{"a": [{"b": 1, "c": 3}, 2]}')) | ||
end | ||
end | ||
end | ||
end |