diff --git a/ee/spec/graphql/mutations/dast/profiles/delete_spec.rb b/ee/spec/graphql/mutations/dast/profiles/delete_spec.rb
index 6985d51208c74c4b3924b02cd8f3b37ad83136fd..e0dc5cf9875f5de940c0de543332d87b8759634c 100644
--- a/ee/spec/graphql/mutations/dast/profiles/delete_spec.rb
+++ b/ee/spec/graphql/mutations/dast/profiles/delete_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::Dast::Profiles::Delete do
+  include GraphqlHelpers
+
   let_it_be(:project) { create(:project) }
   let_it_be(:user) { create(:user) }
   let_it_be(:dast_profile) { create(:dast_profile, project: project) }
@@ -36,7 +38,7 @@
       end
 
       context 'when the dast_profile does not exist' do
-        let(:dast_profile_gid) { Gitlab::GlobalId.build(nil, model_name: 'Dast::Profile', id: 'does_not_exist') }
+        let(:dast_profile_gid) { global_id_of(id: 'does_not_exist', model_name: 'Dast::Profile') }
 
         it 'raises an exception' do
           expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
diff --git a/ee/spec/graphql/mutations/dast/profiles/run_spec.rb b/ee/spec/graphql/mutations/dast/profiles/run_spec.rb
index a925848a2bf9b3e2ce11fd46f04a064324f456d4..52381bdd9df781600e34d54a526057b6506d9687 100644
--- a/ee/spec/graphql/mutations/dast/profiles/run_spec.rb
+++ b/ee/spec/graphql/mutations/dast/profiles/run_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::Dast::Profiles::Run do
+  include GraphqlHelpers
+
   let_it_be_with_refind(:project) { create(:project, :repository) }
 
   let_it_be(:user) { create(:user) }
@@ -60,7 +62,7 @@
         end
 
         context 'when the dast_profile does not exist' do
-          let(:dast_profile_id) { Gitlab::GlobalId.build(nil, model_name: 'Dast::Profile', id: 'does_not_exist') }
+          let(:dast_profile_id) { global_id_of(model_name: 'Dast::Profile', id: 'does_not_exist') }
 
           it 'raises an exception' do
             expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
diff --git a/ee/spec/graphql/mutations/dast/profiles/update_spec.rb b/ee/spec/graphql/mutations/dast/profiles/update_spec.rb
index 7c24fb7117cdaa2b1e567c900a9d8a86c905987e..891f69e54ab0398a5566c13fa4c9c7d3efa2c525 100644
--- a/ee/spec/graphql/mutations/dast/profiles/update_spec.rb
+++ b/ee/spec/graphql/mutations/dast/profiles/update_spec.rb
@@ -159,7 +159,7 @@
         end
 
         context 'when the dast_profile does not exist' do
-          let(:dast_profile_gid) { Gitlab::GlobalId.build(nil, model_name: 'Dast::Profile', id: 'does_not_exist') }
+          let(:dast_profile_gid) { global_id_of(model_name: 'Dast::Profile', id: 'does_not_exist') }
 
           it_behaves_like 'an unrecoverable failure'
         end
diff --git a/ee/spec/graphql/mutations/dast_scanner_profiles/delete_spec.rb b/ee/spec/graphql/mutations/dast_scanner_profiles/delete_spec.rb
index 0e6bed204dfd5eca6caa641f64f1ddbeb3828225..3275e3096ff49d7e83279c7086495c240fcd0f18 100644
--- a/ee/spec/graphql/mutations/dast_scanner_profiles/delete_spec.rb
+++ b/ee/spec/graphql/mutations/dast_scanner_profiles/delete_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::DastScannerProfiles::Delete do
+  include GraphqlHelpers
+
   let_it_be(:project) { create(:project) }
   let_it_be(:user) { create(:user) }
   let_it_be(:dast_scanner_profile) { create(:dast_scanner_profile, project: project) }
@@ -38,7 +40,7 @@
       end
 
       context 'when the dast_scanner_profile does not exist' do
-        let(:dast_scanner_profile_id) { Gitlab::GlobalId.build(nil, model_name: 'DastScannerProfile', id: 'does_not_exist') }
+        let(:dast_scanner_profile_id) { global_id_of(model_name: 'DastScannerProfile', id: 'does_not_exist') }
 
         it 'raises an exception' do
           expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
diff --git a/ee/spec/graphql/mutations/dast_scanner_profiles/update_spec.rb b/ee/spec/graphql/mutations/dast_scanner_profiles/update_spec.rb
index 9f66936eadae139711e128ffeb48459ae99b2044..0006158e590834ece1cd2795c5660ef1df14d066 100644
--- a/ee/spec/graphql/mutations/dast_scanner_profiles/update_spec.rb
+++ b/ee/spec/graphql/mutations/dast_scanner_profiles/update_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::DastScannerProfiles::Update do
+  include GraphqlHelpers
+
   let_it_be(:group) { create(:group) }
   let_it_be(:project) { create(:project, group: group) }
   let_it_be(:user) { create(:user) }
@@ -83,7 +85,7 @@
         end
 
         context 'when dast scanner profile does not exist' do
-          let(:scanner_profile_id) { Gitlab::GlobalId.build(nil, model_name: 'DastScannerProfile', id: 'does_not_exist') }
+          let(:scanner_profile_id) { global_id_of(model_name: 'DastScannerProfile', id: 'does_not_exist') }
 
           it 'raises an exception' do
             expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
diff --git a/ee/spec/graphql/mutations/namespaces/increase_storage_temporarily_spec.rb b/ee/spec/graphql/mutations/namespaces/increase_storage_temporarily_spec.rb
index 530023a4a63900067890fb7f78a82b39f6f692f5..c1391565a4ad3d4f289296f9605a18a2dc08f497 100644
--- a/ee/spec/graphql/mutations/namespaces/increase_storage_temporarily_spec.rb
+++ b/ee/spec/graphql/mutations/namespaces/increase_storage_temporarily_spec.rb
@@ -9,7 +9,7 @@
   subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
 
   describe '#resolve' do
-    subject { mutation.resolve(id: namespace.to_global_id.to_s) }
+    subject { mutation.resolve(id: namespace.to_global_id) }
 
     before do
       allow_next_instance_of(EE::Namespace::RootStorageSize, namespace) do |root_storage|
diff --git a/ee/spec/graphql/mutations/vulnerabilities/dismiss_spec.rb b/ee/spec/graphql/mutations/vulnerabilities/dismiss_spec.rb
index 5eeacee54997263caf72b6ee7f9d510ee5f87342..927f5d101c2d3be330f0a3dfe8af5c78cc871831 100644
--- a/ee/spec/graphql/mutations/vulnerabilities/dismiss_spec.rb
+++ b/ee/spec/graphql/mutations/vulnerabilities/dismiss_spec.rb
@@ -9,7 +9,7 @@
   describe '#resolve' do
     let_it_be(:vulnerability) { create(:vulnerability, :with_findings) }
     let_it_be(:user) { create(:user) }
-    let_it_be(:vulnerability_id) { GitlabSchema.id_from_object(vulnerability).to_s }
+    let_it_be(:vulnerability_id) { GitlabSchema.id_from_object(vulnerability) }
 
     let(:comment) { 'Dismissal Feedback' }
     let(:mutated_vulnerability) { subject[:vulnerability] }
diff --git a/ee/spec/graphql/mutations/vulnerabilities/finding_dismiss_spec.rb b/ee/spec/graphql/mutations/vulnerabilities/finding_dismiss_spec.rb
index 1a5f260f2660c60b240e27e1ac28c42a95d705f0..21a26c7f1d205596f4bb59c07e5ebc1b05d78595 100644
--- a/ee/spec/graphql/mutations/vulnerabilities/finding_dismiss_spec.rb
+++ b/ee/spec/graphql/mutations/vulnerabilities/finding_dismiss_spec.rb
@@ -9,7 +9,7 @@
   describe '#resolve' do
     let_it_be(:finding) { create(:vulnerabilities_finding) }
     let_it_be(:user) { create(:user) }
-    let_it_be(:finding_id) { GitlabSchema.id_from_object(finding).to_s }
+    let_it_be(:finding_id) { global_id_of(finding) }
 
     let(:comment) { 'Dismissal Feedback' }
     let(:mutated_finding) { subject[:finding] }
diff --git a/ee/spec/graphql/resolvers/network_policy_resolver_spec.rb b/ee/spec/graphql/resolvers/network_policy_resolver_spec.rb
index ca0407b3b33ebbba71ee1e4928a2c82d9b109eab..257ec17868b7b54c55a81d90e147e82e6bb632ab 100644
--- a/ee/spec/graphql/resolvers/network_policy_resolver_spec.rb
+++ b/ee/spec/graphql/resolvers/network_policy_resolver_spec.rb
@@ -86,7 +86,7 @@
         end
 
         context 'when environment_id is provided' do
-          let(:environment_id) { 'gid://gitlab/Environment/31' }
+          let(:environment_id) { global_id_of(model_name: 'Environment', id: 31) }
 
           it 'uses NetworkPolicies::ResourceService with resolved environment_id to fetch policies' do
             expect(NetworkPolicies::ResourcesService).to receive(:new).with(project: project, environment_id: '31')
diff --git a/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb b/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb
index 9f58329fe8b90f616ac9be47a65745dac83af693..e9071cc6e88c7f193995ecf300201034791c0854 100644
--- a/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb
+++ b/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb
@@ -45,10 +45,6 @@ def pagination_query(params = {})
     let(:epic_fields) { all_graphql_fields_for('epics'.classify) }
     let(:all_records) { [epic2.to_global_id.to_s, epic1.to_global_id.to_s, epic3.to_global_id.to_s] }
 
-    def pagination_results_data(nodes)
-      nodes.map { |list| list['id'] }
-    end
-
     it_behaves_like 'sorted paginated query' do
       # currently we don't support custom sorting for epic lists,
       # nil value will be ignored by ::Graphql::Arguments
@@ -66,8 +62,8 @@ def pagination_results_data(nodes)
 
       post_graphql(query, current_user: current_user)
 
-      boards = graphql_data_at(*data_path, :nodes, :id)
-      expect(boards).to contain_exactly(global_id_of(epic3))
+      boards = graphql_data_at(*data_path, :nodes)
+      expect(boards).to contain_exactly(a_graphql_entity_for(epic3))
     end
 
     context 'when negated' do
@@ -77,8 +73,8 @@ def pagination_results_data(nodes)
 
         post_graphql(query, current_user: current_user)
 
-        boards = graphql_data_at(*data_path, :nodes, :id)
-        expect(boards).to contain_exactly(global_id_of(epic1), global_id_of(epic2))
+        boards = graphql_data_at(*data_path, :nodes)
+        expect(boards).to contain_exactly(a_graphql_entity_for(epic1), a_graphql_entity_for(epic2))
       end
     end
   end
diff --git a/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb b/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb
index b4042fe83dbb2e503c83fea42d50c157bfbc7691..5e78c6713d4111b3efd993c83bc5e9aaad3ec838 100644
--- a/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb
+++ b/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb
@@ -78,8 +78,11 @@ def pagination_results_data(nodes)
         post_graphql(pagination_query, current_user: current_user)
 
         # ordered by list_type then position - backlog first and closed last.
-        assert_field_value('id', [global_id_of(list3), global_id_of(list1), global_id_of(list2)])
-        assert_field_value('collapsed', [false, true, false])
+        expect(list_nodes).to match [
+          a_graphql_entity_for(list3, collapsed: false),
+          a_graphql_entity_for(list1, collapsed: true),
+          a_graphql_entity_for(list2, collapsed: false)
+        ]
       end
 
       it 'returns the correct metadata values' do
@@ -97,13 +100,11 @@ def pagination_results_data(nodes)
         params = { epicFilters: { labelName: label.title, confidential: false } }
         post_graphql(pagination_query(params), current_user: current_user)
 
-        assert_field_value('epicsCount', [1, 0, 0])
-        expected_metadata = [
-          { 'epicsCount' => 1, 'totalWeight' => 7 },
-          { 'epicsCount' => 0, 'totalWeight' => nil },
-          { 'epicsCount' => 0, 'totalWeight' => nil }
+        expect(list_nodes).to match [
+          a_hash_including('epicsCount' => 1, 'metadata' => { 'epicsCount' => 1, 'totalWeight' => 7 }),
+          a_hash_including('epicsCount' => 0, 'metadata' => { 'epicsCount' => 0, 'totalWeight' => nil }),
+          a_hash_including('epicsCount' => 0, 'metadata' => { 'epicsCount' => 0, 'totalWeight' => nil })
         ]
-        assert_field_value('metadata', expected_metadata)
       end
 
       context 'when totalWeight not requested' do
@@ -112,17 +113,17 @@ def pagination_results_data(nodes)
         it 'does not required the value from the service' do
           post_graphql(pagination_query, current_user: current_user)
 
-          expect(dig_data('metadata').first.keys).to match_array(['epicsCount'])
+          expect(list_nodes('metadata').first.keys).to match_array(['epicsCount'])
         end
       end
     end
   end
 
   def assert_field_value(field, expected_value)
-    expect(dig_data(field)).to eq(expected_value)
+    expect(list_nodes(field)).to eq(expected_value)
   end
 
-  def dig_data(field)
-    graphql_dig_at(graphql_data, 'group', 'epicBoard', 'lists', 'nodes', field)
+  def list_nodes(*fields)
+    graphql_dig_at(graphql_data, 'group', 'epicBoard', 'lists', 'nodes', *fields)
   end
 end
diff --git a/ee/spec/requests/api/graphql/instance_security_dashboard_spec.rb b/ee/spec/requests/api/graphql/instance_security_dashboard_spec.rb
index a1204eca52644428b727e8a6252f77f94374cb8d..42d85d9235df7e0a94861152deba68381fb3b27d 100644
--- a/ee/spec/requests/api/graphql/instance_security_dashboard_spec.rb
+++ b/ee/spec/requests/api/graphql/instance_security_dashboard_spec.rb
@@ -34,7 +34,7 @@
         end
 
         it 'finds only projects that were added to instance security dashboard' do
-          expect(projects).to contain_exactly({ "id" => global_id_of(project) })
+          expect(projects).to contain_exactly(a_graphql_entity_for(project))
         end
       end
     end
diff --git a/ee/spec/requests/api/graphql/merge_request_reviewer_spec.rb b/ee/spec/requests/api/graphql/merge_request_reviewer_spec.rb
index a508ff6820a3601530a05143c78687266bcdd3ba..db31725f444d2d4b6729343dfb08d2d4e262607b 100644
--- a/ee/spec/requests/api/graphql/merge_request_reviewer_spec.rb
+++ b/ee/spec/requests/api/graphql/merge_request_reviewer_spec.rb
@@ -58,9 +58,8 @@
       end
 
       it 'returns appropriate data' do
-        the_rule = {
-          'id' => global_id_of(rule),
-          'name' => rule.name,
+        the_rule = a_graphql_entity_for(
+          rule, :name,
           'type' => 'CODE_OWNER',
           'approvalsRequired' => 0,
           'approved' => true,
@@ -68,11 +67,11 @@
           'overridden' => false,
           'section' => 'codeowners',
           'sourceRule' => nil
-        }
+        )
 
         post_graphql(query)
 
-        expect(interaction['applicableApprovalRules'].first).to include(the_rule)
+        expect(interaction['applicableApprovalRules'].first).to match the_rule
       end
     end
   end
diff --git a/ee/spec/requests/api/graphql/merge_requests/approval_state_spec.rb b/ee/spec/requests/api/graphql/merge_requests/approval_state_spec.rb
index e54756293e5d55e157ce0227187395fdf8c4849f..5f5ecea1676fe59eb051dc1134b9b359ee332566 100644
--- a/ee/spec/requests/api/graphql/merge_requests/approval_state_spec.rb
+++ b/ee/spec/requests/api/graphql/merge_requests/approval_state_spec.rb
@@ -93,24 +93,25 @@
       it 'returns appropriate data' do
         post_graphql(query)
 
-        expect(approval_state).to eq({
+        expect(approval_state).to match a_hash_including(
           'approvalRulesOverwritten' => false,
-          'rules' => [{
-            'approvalsRequired' => 0,
-            'approved' => true,
-            'approvedBy' => { 'nodes' => [] },
-            'containsHiddenGroups' => false,
-            'eligibleApprovers' => [{ 'id' => global_id_of(user) }],
-            'groups' => { 'nodes' => [] },
-            'id' => global_id_of(code_owner_rule),
-            'name' => code_owner_rule.name,
-            'overridden' => false,
-            'section' => 'codeowners',
-            'sourceRule' => nil,
-            'type' => 'CODE_OWNER',
-            'users' => { 'nodes' => [{ 'id' => global_id_of(user) }] }
-          }]
-        })
+          'rules' => contain_exactly(
+            a_graphql_entity_for(
+              code_owner_rule, :name,
+              'approvalsRequired' => 0,
+              'approved' => true,
+              'approvedBy' => { 'nodes' => [] },
+              'containsHiddenGroups' => false,
+              'eligibleApprovers' => contain_exactly(a_graphql_entity_for(user)),
+              'groups' => { 'nodes' => [] },
+              'overridden' => false,
+              'section' => 'codeowners',
+              'sourceRule' => nil,
+              'type' => 'CODE_OWNER',
+              'users' => { 'nodes' => contain_exactly(a_graphql_entity_for(user)) }
+            )
+          )
+        )
       end
     end
   end
diff --git a/ee/spec/requests/api/graphql/mutations/dast/profiles/create_spec.rb b/ee/spec/requests/api/graphql/mutations/dast/profiles/create_spec.rb
index eab23cb92da1b84008512635e016e49e22b0fcbe..fddba08e0f26d7c521d8f8b7979140ff7923e102 100644
--- a/ee/spec/requests/api/graphql/mutations/dast/profiles/create_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/dast/profiles/create_spec.rb
@@ -33,7 +33,7 @@
     it 'returns dastProfile.id' do
       subject
 
-      expect(mutation_response.dig('dastProfile', 'id')).to eq(global_id_of(dast_profile))
+      expect(mutation_response['dastProfile']).to match a_graphql_entity_for(dast_profile)
     end
 
     it 'returns dastProfile.editPath' do
diff --git a/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/create_spec.rb b/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/create_spec.rb
index 3c10a6a8cd6ab6d3a1b47a103a6358639d66a2ed..5f23280a79081b9859ec4a5d5a3d7733da8d9411 100644
--- a/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/create_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/create_spec.rb
@@ -26,11 +26,11 @@
     it 'returns the dast_scanner_profile id' do
       post_graphql_mutation(mutation, current_user: current_user)
 
-      expect(mutation_response['id']).to eq(global_id_of(dast_scanner_profile))
+      expect(mutation_response).to match a_graphql_entity_for(dast_scanner_profile)
       expect(mutation_response).to have_key('dastScannerProfile')
 
       profile = mutation_response['dastScannerProfile']
-      expect(profile['id']).to eq(global_id_of(dast_scanner_profile))
+      expect(profile).to match a_graphql_entity_for(dast_scanner_profile)
       expect(profile['profileName']).to eq(dast_scanner_profile.name)
     end
 
diff --git a/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/update_spec.rb b/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/update_spec.rb
index c999796fc76df6bd9385a1308ecb30d1f65165d3..43c6be0a1738b44a2f9517981df22d494c46893e 100644
--- a/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/update_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/dast_scanner_profiles/update_spec.rb
@@ -39,15 +39,14 @@ def mutation_response
   it_behaves_like 'an on-demand scan mutation when user cannot run an on-demand scan'
 
   it_behaves_like 'an on-demand scan mutation when user can run an on-demand scan' do
-    it 'returns the dast_scanner_profile' do
+    it 'returns the dast_scanner_profile', :aggregate_failures do
       subject
 
-      expect(mutation_response['id']).to eq(global_id_of(dast_scanner_profile))
+      expect(mutation_response).to match a_graphql_entity_for(dast_scanner_profile)
       expect(mutation_response).to have_key('dastScannerProfile')
 
       profile = mutation_response['dastScannerProfile']
-      expect(profile['id']).to eq(global_id_of(dast_scanner_profile))
-      expect(profile['profileName']).to eq(new_profile_name)
+      expect(profile).to match a_graphql_entity_for(dast_scanner_profile, profile_name: new_profile_name)
     end
 
     it 'updates the dast_scanner_profile' do
diff --git a/ee/spec/requests/api/graphql/mutations/dast_site_profiles/create_spec.rb b/ee/spec/requests/api/graphql/mutations/dast_site_profiles/create_spec.rb
index fae7544c1406d9aa20a1baf84a0460dedac8eb3f..221a0a0bc0750b58d3c962ef8b21cb19e129d775 100644
--- a/ee/spec/requests/api/graphql/mutations/dast_site_profiles/create_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/dast_site_profiles/create_spec.rb
@@ -43,7 +43,7 @@
 
       dast_site_profile = DastSiteProfile.find_by(project: project, name: profile_name)
 
-      expect(mutation_response).to include('id' => global_id_of(dast_site_profile))
+      expect(mutation_response).to match(a_graphql_entity_for(dast_site_profile))
     end
   end
 end
diff --git a/ee/spec/requests/api/graphql/mutations/dast_site_tokens/create_spec.rb b/ee/spec/requests/api/graphql/mutations/dast_site_tokens/create_spec.rb
index d7c462418729543cd20353535b4957acf162da64..0904a2df2c05d1cde9677aa58da3aa0d40beb190 100644
--- a/ee/spec/requests/api/graphql/mutations/dast_site_tokens/create_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/dast_site_tokens/create_spec.rb
@@ -31,7 +31,7 @@
 
       dast_site_token = DastSiteToken.find_by!(project: project, token: uuid)
 
-      expect(mutation_response["id"]).to eq(global_id_of(dast_site_token))
+      expect(mutation_response).to match a_graphql_entity_for(dast_site_token)
     end
 
     it 'creates a new dast_site_token' do
diff --git a/ee/spec/requests/api/graphql/mutations/dast_site_validations/create_spec.rb b/ee/spec/requests/api/graphql/mutations/dast_site_validations/create_spec.rb
index ceee690c3a9408fa034e125f295435145b7b8795..513a478be34ebb023244b1817a7730057f1638ce 100644
--- a/ee/spec/requests/api/graphql/mutations/dast_site_validations/create_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/dast_site_validations/create_spec.rb
@@ -31,7 +31,7 @@
 
       dast_site_validation = DastSiteValidation.find_by!(url_path: validation_path)
 
-      expect(mutation_response["id"]).to eq(global_id_of(dast_site_validation))
+      expect(mutation_response).to match a_graphql_entity_for(dast_site_validation)
     end
 
     it 'creates a new dast_site_validation' do
diff --git a/ee/spec/requests/api/graphql/mutations/incident_management/oncall_rotation/create_spec.rb b/ee/spec/requests/api/graphql/mutations/incident_management/oncall_rotation/create_spec.rb
index d6142035aeb112205da2fb9222db01ea476db6ea..08d2f9994f6321baff39a275a979450e559d8488 100644
--- a/ee/spec/requests/api/graphql/mutations/incident_management/oncall_rotation/create_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/incident_management/oncall_rotation/create_spec.rb
@@ -71,8 +71,8 @@
 
     expect(response).to have_gitlab_http_status(:success)
 
-    expect(oncall_rotation_response.slice(*%w[id name length lengthUnit])).to eq(
-      'id' => global_id_of(new_oncall_rotation),
+    expect(oncall_rotation_response.slice(*%w[id name length lengthUnit])).to match a_graphql_entity_for(
+      new_oncall_rotation,
       'name' => args[:name],
       'length' => 1,
       'lengthUnit' => 'DAYS'
@@ -81,13 +81,8 @@
     start_time = "#{args[:starts_at][:date]} #{args[:starts_at][:time]}".in_time_zone(schedule.timezone)
     expect(Time.parse(oncall_rotation_response['startsAt'])).to eq(start_time)
 
-    expect(oncall_rotation_response.dig('participants', 'nodes')).to contain_exactly(
-      {
-        'user' => {
-          'id' => global_id_of(current_user),
-          'username' => current_user.username
-        }
-      }
+    expect(oncall_rotation_response.dig('participants', 'nodes')).to contain_exactly a_hash_including(
+      'user' => a_graphql_entity_for(current_user, :username)
     )
   end
 
diff --git a/ee/spec/requests/api/graphql/mutations/issues/set_escalation_policy_spec.rb b/ee/spec/requests/api/graphql/mutations/issues/set_escalation_policy_spec.rb
index e7009b150343261ce3a6c8b3123d19ef85cddc8e..21574e8e4abaddce546eacecd78be1ff7202883b 100644
--- a/ee/spec/requests/api/graphql/mutations/issues/set_escalation_policy_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/issues/set_escalation_policy_spec.rb
@@ -67,8 +67,9 @@
 
     expect(response).to have_gitlab_http_status(:success)
     expect(mutation_response['errors']).to be_empty
-    expect(mutation_response['issue']['escalationPolicy']['id']).to eq(global_id_of(escalation_policy))
-    expect(mutation_response['issue']['escalationPolicy']['name']).to eq(escalation_policy.name)
+    expect(mutation_response['issue']['escalationPolicy']).to match a_graphql_entity_for(
+      escalation_policy, :name
+    )
     expect(escalation_status.reload.policy).to eq(escalation_policy)
   end
 
diff --git a/ee/spec/requests/api/graphql/mutations/issues/update_spec.rb b/ee/spec/requests/api/graphql/mutations/issues/update_spec.rb
index edbd20be2bae382b9fdc4126ce2aee0dbc87bf59..31cadccb8e622145af158a0958aa0676aea05331 100644
--- a/ee/spec/requests/api/graphql/mutations/issues/update_spec.rb
+++ b/ee/spec/requests/api/graphql/mutations/issues/update_spec.rb
@@ -51,7 +51,7 @@
       expect(response).to have_gitlab_http_status(:success)
       expect(graphql_errors).to be_blank
       expect(mutated_issue).to include(
-        'epic' => include('id' => global_id_of(epic))
+        'epic' => a_graphql_entity_for(epic)
       )
     end
 
diff --git a/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb b/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb
index 4e2e7b273dd6235cdaddd37579e1ced67d3f4e54..d96845aa264c1b548bb963aea2c63480c5dd38be 100644
--- a/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb
+++ b/ee/spec/requests/api/graphql/namespace/compliance_frameworks_spec.rb
@@ -28,8 +28,8 @@
       post_graphql(query, current_user: current_user)
 
       expect(graphql_data_at(*path)).to contain_exactly(
-        a_hash_including('id' => global_id_of(compliance_framework_1)),
-        a_hash_including('id' => global_id_of(compliance_framework_2))
+        a_graphql_entity_for(compliance_framework_1),
+        a_graphql_entity_for(compliance_framework_2)
       )
     end
 
@@ -43,7 +43,7 @@
       it 'returns only a single compliance framework' do
         post_graphql(query, current_user: current_user)
 
-        expect(graphql_data_at(:namespace, :complianceFrameworks, :nodes).map { |n| n['id'] }).to contain_exactly(global_id_of(compliance_framework_1))
+        expect(graphql_data_at(:namespace, :complianceFrameworks, :nodes)).to contain_exactly(a_graphql_entity_for(compliance_framework_1))
       end
     end
 
diff --git a/ee/spec/requests/api/graphql/project/alert_management/http_integrations_spec.rb b/ee/spec/requests/api/graphql/project/alert_management/http_integrations_spec.rb
index b8a23767f6bac08905335f7ad140f7e07a3437c9..d6b04a97437e6ab52205bbff3d835e005e841168 100644
--- a/ee/spec/requests/api/graphql/project/alert_management/http_integrations_spec.rb
+++ b/ee/spec/requests/api/graphql/project/alert_management/http_integrations_spec.rb
@@ -107,13 +107,10 @@
 
         it 'returns the correct properties of the integrations' do
           expect(integrations).to include(
-            {
-              'id' => global_id_of(active_http_integration),
+            a_graphql_entity_for(
+              active_http_integration,
+              :token, :name, :active, :url,
               'type' => 'HTTP',
-              'name' => active_http_integration.name,
-              'active' => active_http_integration.active,
-              'token' => active_http_integration.token,
-              'url' => active_http_integration.url,
               'apiUrl' => nil,
               'payloadExample' => payload_example.to_json,
               'payloadAttributeMappings' => [
@@ -142,19 +139,16 @@
                   'type' => 'STRING'
                 }
               ]
-            },
-            {
-              'id' => global_id_of(inactive_http_integration),
+            ),
+            a_graphql_entity_for(
+              inactive_http_integration,
+              :name, :active, :token, :url,
               'type' => 'HTTP',
-              'name' => inactive_http_integration.name,
-              'active' => inactive_http_integration.active,
-              'token' => inactive_http_integration.token,
-              'url' => inactive_http_integration.url,
               'apiUrl' => nil,
               'payloadExample' => "{}",
               'payloadAttributeMappings' => [],
               'payloadAlertFields' => []
-            }
+            )
           )
         end
       end
@@ -164,22 +158,15 @@
 
         it_behaves_like 'a working graphql query'
 
-        specify { expect(integrations).to be_one }
-
         it 'returns the correct properties of the integration' do
-          expect(integrations).to include(
-            {
-              'id' => global_id_of(inactive_http_integration),
-              'type' => 'HTTP',
-              'name' => inactive_http_integration.name,
-              'active' => inactive_http_integration.active,
-              'token' => inactive_http_integration.token,
-              'url' => inactive_http_integration.url,
-              'apiUrl' => nil,
-              'payloadExample' => "{}",
-              'payloadAttributeMappings' => [],
-              'payloadAlertFields' => []
-            }
+          expect(integrations).to contain_exactly a_graphql_entity_for(
+            inactive_http_integration,
+            :name, :active, :token, :url,
+            'type' => 'HTTP',
+            'apiUrl' => nil,
+            'payloadExample' => "{}",
+            'payloadAttributeMappings' => [],
+            'payloadAlertFields' => []
           )
         end
       end
diff --git a/ee/spec/requests/projects/on_demand_scans_controller_spec.rb b/ee/spec/requests/projects/on_demand_scans_controller_spec.rb
index 97593b2243f02a379d96ad4e08f127a4d98c702a..0cd5fcd627a2051866f0bd26f027a4d4084026fe 100644
--- a/ee/spec/requests/projects/on_demand_scans_controller_spec.rb
+++ b/ee/spec/requests/projects/on_demand_scans_controller_spec.rb
@@ -106,12 +106,12 @@
           get edit_path
 
           json_data = {
-            id: global_id_of(dast_profile),
+            **a_graphql_entity_for(dast_profile),
             name: dast_profile.name,
             description: dast_profile.description,
             branch: { name: project.default_branch_or_main },
-            dastSiteProfile: { id: global_id_of(DastSiteProfile.new(id: dast_profile.dast_site_profile_id)) },
-            dastScannerProfile: { id: global_id_of(DastScannerProfile.new(id: dast_profile.dast_scanner_profile_id)) },
+            dastSiteProfile: a_graphql_entity_for(DastSiteProfile.new(id: dast_profile.dast_site_profile_id)),
+            dastScannerProfile: a_graphql_entity_for(DastScannerProfile.new(id: dast_profile.dast_scanner_profile_id)),
             dastProfileSchedule: {
               active: dast_profile_schedule.active,
               cadence: {
@@ -125,7 +125,7 @@
 
           on_demand_div = Nokogiri::HTML.parse(response.body).at_css('div#js-on-demand-scans-form')
 
-          expect(on_demand_div.attributes['data-dast-scan'].value).to include(json_data)
+          expect(on_demand_div.attributes['data-dast-scan'].value).to eq json_data
         end
       end
 
diff --git a/ee/spec/requests/projects/security/dast_site_profiles_controller_spec.rb b/ee/spec/requests/projects/security/dast_site_profiles_controller_spec.rb
index 1432748112d31df3f9fb8f020fd2734c8f618606..b713a0b0784964dd7d84aecbd8f318a8de4e4b76 100644
--- a/ee/spec/requests/projects/security/dast_site_profiles_controller_spec.rb
+++ b/ee/spec/requests/projects/security/dast_site_profiles_controller_spec.rb
@@ -106,14 +106,12 @@ def with_user_authorized
         it 'includes a serialized dast_profile in the response body' do
           get edit_path
 
-          json_data = {
-            id: global_id_of(dast_site_profile),
-            name: dast_site_profile.name,
+          json_data = a_graphql_entity_for(
+            dast_site_profile, :name, :excluded_urls, :referenced_in_security_policies,
             targetUrl:  dast_site_profile.dast_site.url,
             targetType: dast_site_profile.target_type.upcase,
-            excludedUrls:  dast_site_profile.excluded_urls,
             requestHeaders:  Dast::SiteProfilePresenter::REDACTED_REQUEST_HEADERS,
-            auth: {
+            auth: a_graphql_entity_for(
               enabled: dast_site_profile.auth_enabled,
               url: dast_site_profile.auth_url,
               username: dast_site_profile.auth_username,
@@ -121,13 +119,13 @@ def with_user_authorized
               password: Dast::SiteProfilePresenter::REDACTED_PASSWORD,
               passwordField: dast_site_profile.auth_password_field,
               submitField: dast_site_profile.auth_submit_field
-            },
-            referencedInSecurityPolicies: dast_site_profile.referenced_in_security_policies
-          }.to_json
+            )
+          )
 
           form = Nokogiri::HTML.parse(response.body).at_css('div.js-dast-site-profile-form')
+          serialized = Gitlab::Json.parse(form.attributes['data-site-profile'].value)
 
-          expect(form.attributes['data-site-profile'].value).to include(json_data)
+          expect(serialized).to match(json_data)
         end
       end
 
diff --git a/spec/graphql/mutations/container_repositories/destroy_spec.rb b/spec/graphql/mutations/container_repositories/destroy_spec.rb
index 3903196a51115f51e642bca450b4e3c07b241f62..7c674dddb1514e850eb7fa516a8568a507f71961 100644
--- a/spec/graphql/mutations/container_repositories/destroy_spec.rb
+++ b/spec/graphql/mutations/container_repositories/destroy_spec.rb
@@ -9,7 +9,7 @@
   let_it_be(:user) { create(:user) }
 
   let(:project) { container_repository.project }
-  let(:id) { container_repository.to_global_id.to_s }
+  let(:id) { container_repository.to_global_id }
 
   specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
 
diff --git a/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb b/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb
index f22d9ffe753b3fee9859c3dbf4daedb8fad5fdc7..3e5f28ee244fd1e571c27f00085e88b9b718aa5c 100644
--- a/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb
+++ b/spec/graphql/mutations/container_repositories/destroy_tags_spec.rb
@@ -3,10 +3,12 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::ContainerRepositories::DestroyTags do
+  include GraphqlHelpers
+
   include_context 'container repository delete tags service shared context'
   using RSpec::Parameterized::TableSyntax
 
-  let(:id) { repository.to_global_id.to_s }
+  let(:id) { repository.to_global_id }
 
   specify { expect(described_class).to require_graphql_authorizations(:destroy_container_image) }
 
@@ -67,8 +69,8 @@
       end
     end
 
-    context 'with invalid id' do
-      let(:id) { 'gid://gitlab/ContainerRepository/5555' }
+    context 'with non-existing id' do
+      let(:id) { global_id_of(id: non_existing_record_id, model_name: 'ContainerRepository') }
 
       it_behaves_like 'denying access to container respository'
     end
diff --git a/spec/graphql/mutations/customer_relations/contacts/create_spec.rb b/spec/graphql/mutations/customer_relations/contacts/create_spec.rb
index d17d11305b1fa8ea2a28764d6de82803f2dfd1f4..dafc7b4c367c2a2738ba77d0a2f9a1b5f6f3a14f 100644
--- a/spec/graphql/mutations/customer_relations/contacts/create_spec.rb
+++ b/spec/graphql/mutations/customer_relations/contacts/create_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::CustomerRelations::Contacts::Create do
+  include GraphqlHelpers
+
   let_it_be(:user) { create(:user) }
 
   let(:group) { create(:group, :crm_enabled) }
@@ -78,9 +80,9 @@
           end
         end
 
-        context 'when organization_id is invalid' do
+        context 'when organization does not exist' do
           before do
-            valid_params[:organization_id] = "gid://gitlab/CustomerRelations::Organization/#{non_existing_record_id}"
+            valid_params[:organization_id] = global_id_of(model_name: 'CustomerRelations::Organization', id: non_existing_record_id)
           end
 
           it 'returns the relevant error' do
diff --git a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
index 2041b86d6e7656dd122bbb24785897f8e0f5a249..e5dc6f85c2ace44dea402b524448683958079c49 100644
--- a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::Discussions::ToggleResolve do
+  include GraphqlHelpers
+
   subject(:mutation) do
     described_class.new(object: nil, context: { current_user: user }, field: nil)
   end
@@ -15,7 +17,7 @@
       mutation.resolve(id: id_arg, resolve: resolve_arg)
     end
 
-    let(:id_arg) { discussion.to_global_id.to_s }
+    let(:id_arg) { global_id_of(discussion) }
     let(:resolve_arg) { true }
     let(:mutated_discussion) { subject[:discussion] }
     let(:errors) { subject[:errors] }
@@ -36,7 +38,7 @@
         let_it_be(:user) { create(:user, developer_projects: [project]) }
 
         context 'when discussion cannot be found' do
-          let(:id_arg) { "#{discussion.to_global_id}foo" }
+          let(:id_arg) { global_id_of(id: non_existing_record_id, model_name: discussion.class.name) }
 
           it 'raises an error' do
             expect { subject }.to raise_error(
@@ -52,7 +54,7 @@
           it 'raises an error' do
             expect { subject }.to raise_error(
               GraphQL::CoercionError,
-              "\"#{discussion.to_global_id}\" does not represent an instance of Discussion"
+              "\"#{global_id_of(discussion)}\" does not represent an instance of Discussion"
             )
           end
         end
diff --git a/spec/graphql/mutations/environments/canary_ingress/update_spec.rb b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb
index fdf9cbaf25b447b7670772ba72664069c0d9d535..e719ca050a86d72de1589ac25d5aae4ac78d043a 100644
--- a/spec/graphql/mutations/environments/canary_ingress/update_spec.rb
+++ b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb
@@ -20,7 +20,7 @@
   describe '#resolve' do
     subject { mutation.resolve(id: environment_id, weight: weight) }
 
-    let(:environment_id) { environment.to_global_id.to_s }
+    let(:environment_id) { environment.to_global_id }
     let(:weight) { 50 }
     let(:update_service) { double('update_service') }
 
diff --git a/spec/graphql/mutations/release_asset_links/delete_spec.rb b/spec/graphql/mutations/release_asset_links/delete_spec.rb
index cda292f2ffa99ce70535587ca00b3fd3fd8dc3e9..67576bdda573aecf92956661b4370c44bf3938f0 100644
--- a/spec/graphql/mutations/release_asset_links/delete_spec.rb
+++ b/spec/graphql/mutations/release_asset_links/delete_spec.rb
@@ -52,7 +52,9 @@
       end
 
       context "when the link doesn't exist" do
-        let(:mutation_arguments) { super().merge(id: "gid://gitlab/Releases::Link/#{non_existing_record_id}") }
+        let(:mutation_arguments) do
+          super().merge(id: global_id_of(id: non_existing_record_id, model_name: release_link.class.name))
+        end
 
         it 'raises an error' do
           expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
diff --git a/spec/graphql/mutations/release_asset_links/update_spec.rb b/spec/graphql/mutations/release_asset_links/update_spec.rb
index 6464868733685024b4956c7af7356c2c224f33a5..cb7474123ada01699ea2f874afcdf9bf3aff3ce7 100644
--- a/spec/graphql/mutations/release_asset_links/update_spec.rb
+++ b/spec/graphql/mutations/release_asset_links/update_spec.rb
@@ -186,7 +186,9 @@
     end
 
     context "when the link doesn't exist" do
-      let(:mutation_arguments) { super().merge(id: "gid://gitlab/Releases::Link/#{non_existing_record_id}") }
+      let(:mutation_arguments) do
+        super().merge(id: global_id_of(id: non_existing_record_id, model_name: "Releases::Link"))
+      end
 
       it 'raises an error' do
         expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
diff --git a/spec/graphql/mutations/timelogs/delete_spec.rb b/spec/graphql/mutations/timelogs/delete_spec.rb
index 5012d10f32e316b30e4a6dacfd4ee67c565e9a09..f4a258e0f78b9287e62d1951c69bb3e5f562d23d 100644
--- a/spec/graphql/mutations/timelogs/delete_spec.rb
+++ b/spec/graphql/mutations/timelogs/delete_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe Mutations::Timelogs::Delete do
+  include GraphqlHelpers
+
   let_it_be(:author) { create(:user) }
   let_it_be(:maintainer) { create(:user) }
   let_it_be(:administrator) { create(:user, :admin) }
@@ -11,7 +13,7 @@
   let_it_be_with_reload(:timelog) { create(:timelog, user: author, issue: issue, time_spent: 1800) }
 
   let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
-  let(:timelog_id) { timelog.to_global_id.to_s }
+  let(:timelog_id) { global_id_of(timelog) }
   let(:mutation_arguments) { { id: timelog_id } }
 
   describe '#resolve' do
@@ -21,7 +23,7 @@
 
     context 'when the timelog id is not valid' do
       let(:current_user) { author }
-      let(:timelog_id) { 'gid://gitlab/Timelog/%d' % non_existing_record_id }
+      let(:timelog_id) { global_id_of(model_name: 'Timelog', id: non_existing_record_id) }
 
       it 'raises Gitlab::Graphql::Errors::ResourceNotAvailable' do
         expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
diff --git a/spec/graphql/resolvers/concerns/resolves_ids_spec.rb b/spec/graphql/resolvers/concerns/resolves_ids_spec.rb
index 1dd27c0eff06d92e03169b06ec5c6fbcb248bce2..732b7cd2bbc55a1a7248a0f405dc554f4a68124a 100644
--- a/spec/graphql/resolvers/concerns/resolves_ids_spec.rb
+++ b/spec/graphql/resolvers/concerns/resolves_ids_spec.rb
@@ -3,33 +3,35 @@
 require 'spec_helper'
 
 RSpec.describe ResolvesIds do
+  include GraphqlHelpers
+
   # gid://gitlab/Project/6
   # gid://gitlab/Issue/6
   # gid://gitlab/Project/6 gid://gitlab/Issue/6
   context 'with a single project' do
-    let(:ids) { 'gid://gitlab/Project/6' }
+    let(:ids) { global_id_of(model_name: 'Project', id: 6) }
     let(:type) { ::Types::GlobalIDType[::Project] }
 
     it 'returns the correct array' do
-      expect(resolve_ids).to match_array(['6'])
+      expect(resolve_ids).to contain_exactly('6')
     end
   end
 
   context 'with a single issue' do
-    let(:ids) { 'gid://gitlab/Issue/9' }
+    let(:ids) { global_id_of(model_name: 'Issue', id: 9) }
     let(:type) { ::Types::GlobalIDType[::Issue] }
 
     it 'returns the correct array' do
-      expect(resolve_ids).to match_array(['9'])
+      expect(resolve_ids).to contain_exactly('9')
     end
   end
 
   context 'with multiple users' do
-    let(:ids) { ['gid://gitlab/User/7', 'gid://gitlab/User/13', 'gid://gitlab/User/21'] }
+    let(:ids) { [7, 13, 21].map { global_id_of(model_name: 'User', id: _1) } }
     let(:type) { ::Types::GlobalIDType[::User] }
 
     it 'returns the correct array' do
-      expect(resolve_ids).to match_array(%w[7 13 21])
+      expect(resolve_ids).to eq %w[7 13 21]
     end
   end
 
diff --git a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
index a16e8821cb5641f2cef9053ce1071a8abcbc416f..3fe1ec4b5a4c2643f2d4f934bde9ff539f18a3d4 100644
--- a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb
@@ -14,7 +14,7 @@
 
   let(:current_user) { user }
   let(:object) { issue.design_collection }
-  let(:global_id) { GitlabSchema.id_from_object(design_at_version).to_s }
+  let(:global_id) { GitlabSchema.id_from_object(design_at_version) }
 
   let(:design_at_version) { ::DesignManagement::DesignAtVersion.new(design: design_a, version: version_a) }
 
diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
index 4c8b311687584185a12c2139adf59d990acf1d9a..5b530b68a5b3f809355127d72648f8e7ffb830a2 100644
--- a/spec/graphql/resolvers/design_management/design_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb
@@ -24,7 +24,7 @@
       create(:design, issue: create(:issue, project: project), versions: [create(:design_version)])
     end
 
-    let(:args) { { id: GitlabSchema.id_from_object(first_design).to_s } }
+    let(:args) { { id: GitlabSchema.id_from_object(first_design) } }
     let(:gql_context) { { current_user: current_user } }
 
     before do
@@ -50,7 +50,7 @@
     end
 
     context 'when both arguments have been passed' do
-      let(:args) { { filename: first_design.filename, id: GitlabSchema.id_from_object(first_design).to_s } }
+      let(:args) { { filename: first_design.filename, id: GitlabSchema.id_from_object(first_design) } }
 
       it 'generates an error' do
         expect_graphql_error_to_be_created(::Gitlab::Graphql::Errors::ArgumentError, /may/) do
diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
index b091e58b06f703f805a7eb2457553aead349d28c..64eae14d8883b8d37026d918d1620f24a6630b61 100644
--- a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
+++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb
@@ -109,6 +109,8 @@
   end
 
   def resolve_designs
-    resolve(described_class, obj: issue.design_collection, args: args, ctx: gql_context)
+    Gitlab::Graphql::Lazy.force(
+      resolve(described_class, obj: issue.design_collection, args: args, ctx: gql_context)
+    )
   end
 end
diff --git a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
index 32c53ba23027a4f7e33504650d985ed320c3a287..4b34a750883580c9c3fb1c478820dd31618d1287 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb
@@ -74,6 +74,6 @@ def resolve_error(args = {}, context = { current_user: current_user })
   end
 
   def issue_global_id(issue_id)
-    Gitlab::ErrorTracking::DetailedError.new(id: issue_id).to_global_id.to_s
+    Gitlab::ErrorTracking::DetailedError.new(id: issue_id).to_global_id
   end
 end
diff --git a/spec/graphql/resolvers/timelog_resolver_spec.rb b/spec/graphql/resolvers/timelog_resolver_spec.rb
index 84fa29328294604417b813f24e4b2d7e2d7142c7..da2747fdf72509995d505157226ac5ab37a423b6 100644
--- a/spec/graphql/resolvers/timelog_resolver_spec.rb
+++ b/spec/graphql/resolvers/timelog_resolver_spec.rb
@@ -265,7 +265,7 @@
   context 'when > `default_max_page_size` records' do
     let(:object) { nil }
     let!(:timelog_list) { create_list(:timelog, 101, issue: issue) }
-    let(:args) { { project_id: "gid://gitlab/Project/#{project.id}" } }
+    let(:args) { { project_id: global_id_of(project) } }
     let(:extra_args) { {} }
 
     it 'pagination returns `default_max_page_size` and sets `has_next_page` true' do
diff --git a/spec/graphql/resolvers/work_item_resolver_spec.rb b/spec/graphql/resolvers/work_item_resolver_spec.rb
index bfa0cf1d8a2a1923c294fe0a989b9bb6f9671018..c44ed39510241c2335cad9a523cc15cfd76bac91 100644
--- a/spec/graphql/resolvers/work_item_resolver_spec.rb
+++ b/spec/graphql/resolvers/work_item_resolver_spec.rb
@@ -12,7 +12,7 @@
 
     let(:current_user) { developer }
 
-    subject(:resolved_work_item) { resolve_work_item('id' => work_item.to_gid.to_s) }
+    subject(:resolved_work_item) { resolve_work_item('id' => work_item.to_gid) }
 
     context 'when the user can read the work item' do
       it { is_expected.to eq(work_item) }
diff --git a/spec/graphql/types/terraform/state_version_type_spec.rb b/spec/graphql/types/terraform/state_version_type_spec.rb
index b015a2045da7a179e8a1fdc67cfb13fe6a1c974e..6a17d932d034efe2eb5979204432ed87c1a8efe2 100644
--- a/spec/graphql/types/terraform/state_version_type_spec.rb
+++ b/spec/graphql/types/terraform/state_version_type_spec.rb
@@ -52,8 +52,8 @@
 
     shared_examples 'returning latest version' do
       it 'returns latest version of terraform state' do
-        expect(execute.dig('data', 'project', 'terraformState', 'latestVersion', 'id')).to eq(
-          global_id_of(terraform_state.latest_version)
+        expect(execute.dig('data', 'project', 'terraformState', 'latestVersion')).to match a_graphql_entity_for(
+          terraform_state.latest_version
         )
       end
     end
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index e8fb9daa43b503cc7471c57a303fe82b90f860da..eb206465bce5c5129f8e4e9e50da5c6b9151673f 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -69,6 +69,10 @@ def query(list_params = params)
 
         let(:data_path) { [board_parent_type, :boards, :nodes, 0, :lists] }
 
+        def pagination_results_data(lists)
+          lists
+        end
+
         def pagination_query(params)
           graphql_query_for(
             board_parent_type,
@@ -94,7 +98,7 @@ def pagination_query(params)
             it_behaves_like 'sorted paginated query' do
               let(:sort_param) { }
               let(:first_param) { 2 }
-              let(:all_records) { lists.map { |list| global_id_of(list) } }
+              let(:all_records) { lists.map { |list| a_graphql_entity_for(list) } }
             end
           end
         end
diff --git a/spec/requests/api/graphql/ci/job_spec.rb b/spec/requests/api/graphql/ci/job_spec.rb
index ddb2664d353dcd639e6d9c6c6bb3205be975f543..2fb90dcd92b469b96c2c13a9e00be66e3ce00399 100644
--- a/spec/requests/api/graphql/ci/job_spec.rb
+++ b/spec/requests/api/graphql/ci/job_spec.rb
@@ -47,10 +47,8 @@
       )
       post_graphql(query, current_user: user)
 
-      expect(graphql_data_at(*path)).to match a_hash_including(
-        'id' => global_id_of(job_2),
-        'name' => job_2.name,
-        'allowFailure' => job_2.allow_failure,
+      expect(graphql_data_at(*path)).to match a_graphql_entity_for(
+        job_2, :name, :allow_failure,
         'duration' => 25,
         'kind' => 'BUILD',
         'queuedDuration' => 2.0,
@@ -66,10 +64,7 @@
       it 'retrieves scalar fields' do
         post_graphql(query, current_user: user)
 
-        expect(graphql_data_at(*path)).to match a_hash_including(
-          'id' => global_id_of(job_2),
-          'name' => job_2.name
-        )
+        expect(graphql_data_at(*path)).to match a_graphql_entity_for(job_2, :name)
       end
     end
   end
@@ -102,8 +97,8 @@
         'name' => test_stage.name,
         'jobs' => a_hash_including(
           'nodes' => contain_exactly(
-            a_hash_including('id' => global_id_of(job_2)),
-            a_hash_including('id' => global_id_of(job_3))
+            a_graphql_entity_for(job_2),
+            a_graphql_entity_for(job_3)
           )
         )
       )
diff --git a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
index 922a9ab277ef6ffeb475bb40961158b00a2a8b5f..847fa72522e2a14bb0f1c83ed2d5500d37ea7e1f 100644
--- a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
+++ b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
@@ -127,7 +127,7 @@
 
     let(:query) do
       <<~GQL
-        query($id: ID!, $n: Int) {
+        query($id: ContainerRepositoryID!, $n: Int) {
           containerRepository(id: $id) {
             tags(first: $n) {
               edges {
@@ -157,7 +157,7 @@
 
     let(:query) do
       <<~GQL
-        query($id: ID!, $n: ContainerRepositoryTagSort) {
+        query($id: ContainerRepositoryID!, $n: ContainerRepositoryTagSort) {
           containerRepository(id: $id) {
             tags(sort: $n) {
               edges {
@@ -194,7 +194,7 @@
 
     let(:query) do
       <<~GQL
-        query($id: ID!, $n: String) {
+        query($id: ContainerRepositoryID!, $n: String) {
           containerRepository(id: $id) {
             tags(name: $n) {
               edges {
@@ -232,7 +232,7 @@
 
     let(:query) do
       <<~GQL
-        query($id: ID!) {
+        query($id: ContainerRepositoryID!) {
           containerRepository(id: $id) {
             size
           }
diff --git a/spec/requests/api/graphql/current_user_todos_spec.rb b/spec/requests/api/graphql/current_user_todos_spec.rb
index 7f37abba74aa76a8328816529b9a4e8c10495b8d..da1c893ec2bb684242f33b789d84caebf5b933d2 100644
--- a/spec/requests/api/graphql/current_user_todos_spec.rb
+++ b/spec/requests/api/graphql/current_user_todos_spec.rb
@@ -37,8 +37,8 @@
     post_graphql(query, current_user: current_user)
 
     expect(todoable_response).to contain_exactly(
-      a_hash_including('id' => global_id_of(done_todo)),
-      a_hash_including('id' => global_id_of(pending_todo))
+      a_graphql_entity_for(done_todo),
+      a_graphql_entity_for(pending_todo)
     )
   end
 
@@ -63,7 +63,7 @@
       post_graphql(query, current_user: current_user)
 
       expect(todoable_response).to contain_exactly(
-        a_hash_including('id' => global_id_of(pending_todo))
+        a_graphql_entity_for(pending_todo)
       )
     end
   end
@@ -75,7 +75,7 @@
       post_graphql(query, current_user: current_user)
 
       expect(todoable_response).to contain_exactly(
-        a_hash_including('id' => global_id_of(done_todo))
+        a_graphql_entity_for(done_todo)
       )
     end
   end
diff --git a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
index 3527c8183f6a49e9e0b91b82d27fb02d917f6491..c7149c100b25a6caaf8dedc709085e9ee725c137 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
@@ -122,12 +122,12 @@
     let(:current_user) { owner }
 
     context 'with default sorting' do
-      let_it_be(:descending_manifests) { manifests.reverse.map { |manifest| global_id_of(manifest)} }
+      let_it_be(:descending_manifests) { manifests.reverse.map { |manifest| global_id_of(manifest) } }
 
       it_behaves_like 'sorted paginated query' do
         let(:sort_param) { '' }
         let(:first_param) { 2 }
-        let(:all_records) { descending_manifests }
+        let(:all_records) { descending_manifests.map(&:to_s) }
       end
     end
 
diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb
index 8830320c6f7524a4ceb396a18054d981c6fd1f83..fec866486ae893cff59716669c1f2573ae651e45 100644
--- a/spec/requests/api/graphql/group/group_members_spec.rb
+++ b/spec/requests/api/graphql/group/group_members_spec.rb
@@ -24,8 +24,8 @@
 
       expect(graphql_errors).to be_nil
       expect(graphql_data_at(:group, :group_members, :edges, :node)).to contain_exactly(
-        { 'user' => { 'id' => global_id_of(user_1) } },
-        { 'user' => { 'id' => global_id_of(user_2) } },
+        { 'user' => a_graphql_entity_for(user_1) },
+        { 'user' => a_graphql_entity_for(user_2) },
         'user' => nil
       )
     end
@@ -224,8 +224,8 @@ def member_notification_email_query(group_path)
 
   def expect_array_response(*items)
     expect(response).to have_gitlab_http_status(:success)
-    member_gids = graphql_data_at(:group, :group_members, :edges, :node, :user, :id)
+    members = graphql_data_at(:group, :group_members, :edges, :node, :user)
 
-    expect(member_gids).to match_array(items.map { |u| global_id_of(u) })
+    expect(members).to match_array(items.map { |u| a_graphql_entity_for(u) })
   end
 end
diff --git a/spec/requests/api/graphql/group/merge_requests_spec.rb b/spec/requests/api/graphql/group/merge_requests_spec.rb
index c0faff11c8d2dfede3407d25322e72e7ea0f2efc..434b0d16569962521074a457219e9c1618728799 100644
--- a/spec/requests/api/graphql/group/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/group/merge_requests_spec.rb
@@ -39,7 +39,7 @@
   end
 
   def expected_mrs(mrs)
-    mrs.map { |mr| a_hash_including('id' => global_id_of(mr)) }
+    mrs.map { |mr| a_graphql_entity_for(mr) }
   end
 
   describe 'not passing any arguments' do
diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb
index 2b80b5239c891726590f7c3939d36918f29cca7c..7c51409f9074e617959182b4625723dbfa32c230 100644
--- a/spec/requests/api/graphql/group/milestones_spec.rb
+++ b/spec/requests/api/graphql/group/milestones_spec.rb
@@ -170,10 +170,8 @@ def post_query
     end
 
     it 'returns correct values for scalar fields' do
-      expect(post_query).to eq({
-        'id' => global_id_of(milestone),
-        'title' => milestone.title,
-        'description' => milestone.description,
+      expect(post_query).to match a_graphql_entity_for(
+        milestone, :title, :description,
         'state' => 'active',
         'webPath' => milestone_path(milestone),
         'dueDate' => milestone.due_date.iso8601,
@@ -183,7 +181,7 @@ def post_query
         'projectMilestone' => false,
         'groupMilestone' => true,
         'subgroupMilestone' => false
-      })
+      )
     end
 
     context 'milestone statistics' do
diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb
index 42ca3348384abb3c9750e2ec3bd60b2826946511..05fd6bf30226a7034f03b140e6b5a5ecf5c4a6d7 100644
--- a/spec/requests/api/graphql/issue/issue_spec.rb
+++ b/spec/requests/api/graphql/issue/issue_spec.rb
@@ -8,8 +8,8 @@
   let_it_be(:project) { create(:project) }
   let_it_be(:issue) { create(:issue, project: project) }
   let_it_be(:current_user) { create(:user) }
-  let_it_be(:issue_params) { { 'id' => issue.to_global_id.to_s } }
 
+  let(:issue_params) { { 'id' => global_id_of(issue) } }
   let(:issue_data) { graphql_data['issue'] }
   let(:issue_fields) { all_graphql_fields_for('Issue'.classify) }
 
@@ -100,7 +100,8 @@ def query(fields)
       let_it_be(:issue_fields) { ['moved', 'movedTo { title }'] }
       let_it_be(:new_issue) { create(:issue) }
       let_it_be(:issue) { create(:issue, project: project, moved_to: new_issue) }
-      let_it_be(:issue_params) { { 'id' => issue.to_global_id.to_s } }
+
+      let(:issue_params) { { 'id' => global_id_of(issue) } }
 
       before_all do
         new_issue.project.add_developer(current_user)
diff --git a/spec/requests/api/graphql/merge_request/merge_request_spec.rb b/spec/requests/api/graphql/merge_request/merge_request_spec.rb
index 75dd01a07630a7566d75028cc3f617af37254f35..d89f381753ea5a24cd729ed8ac7c3610db9c0c9d 100644
--- a/spec/requests/api/graphql/merge_request/merge_request_spec.rb
+++ b/spec/requests/api/graphql/merge_request/merge_request_spec.rb
@@ -8,8 +8,8 @@
   let_it_be(:project) { create(:project, :empty_repo) }
   let_it_be(:merge_request) { create(:merge_request, source_project: project) }
   let_it_be(:current_user) { create(:user) }
-  let_it_be(:merge_request_params) { { 'id' => merge_request.to_global_id.to_s } }
 
+  let(:merge_request_params) { { 'id' => global_id_of(merge_request) } }
   let(:merge_request_data) { graphql_data['mergeRequest'] }
   let(:merge_request_fields) { all_graphql_fields_for('MergeRequest'.classify) }
 
diff --git a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
index 02b79dac489a9f97cd683430f74a16138d4e7e23..715507c3cc536db4b4219fc9b2bac176be39fd02 100644
--- a/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_crm_contacts_spec.rb
@@ -15,7 +15,7 @@
   let(:operation_mode) { Types::MutationOperationModeEnum.default_mode }
   let(:initial_contacts) { contacts[0..1] }
   let(:mutation_contacts) { contacts[1..2] }
-  let(:contact_ids) { contact_global_ids(mutation_contacts) }
+  let(:contact_ids) { mutation_contacts.map { global_id_of(_1) } }
   let(:does_not_exist_or_no_permission) { "The resource that you are attempting to access does not exist or you don't have permission to perform this action" }
 
   let(:mutation) do
@@ -45,8 +45,8 @@ def mutation_response
     graphql_mutation_response(:issue_set_crm_contacts)
   end
 
-  def contact_global_ids(contacts)
-    contacts.map { |contact| global_id_of(contact) }
+  def expected_contacts(contacts)
+    contacts.map { |contact| a_graphql_entity_for(contact) }
   end
 
   before do
@@ -58,8 +58,8 @@ def contact_global_ids(contacts)
       it 'updates the issue with correct contacts' do
         post_graphql_mutation(mutation, current_user: user)
 
-        expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id))
-          .to match_array(contact_global_ids(mutation_contacts))
+        expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes))
+          .to match_array(expected_contacts(mutation_contacts))
       end
     end
 
@@ -70,8 +70,8 @@ def contact_global_ids(contacts)
       it 'updates the issue with correct contacts' do
         post_graphql_mutation(mutation, current_user: user)
 
-        expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id))
-          .to match_array(contact_global_ids(initial_contacts + mutation_contacts))
+        expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes))
+          .to match_array(expected_contacts(initial_contacts + mutation_contacts))
       end
     end
 
@@ -82,8 +82,8 @@ def contact_global_ids(contacts)
       it 'updates the issue with correct contacts' do
         post_graphql_mutation(mutation, current_user: user)
 
-        expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes, :id))
-          .to match_array(contact_global_ids(initial_contacts - mutation_contacts))
+        expect(graphql_data_at(:issue_set_crm_contacts, :issue, :customer_relations_contacts, :nodes))
+          .to match_array(expected_contacts(initial_contacts - mutation_contacts))
       end
     end
   end
@@ -117,7 +117,7 @@ def contact_global_ids(contacts)
     it_behaves_like 'successful mutation'
 
     context 'when the contact does not exist' do
-      let(:contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
+      let(:contact_ids) { [global_id_of(model_name: 'CustomerRelations::Contact', id: non_existing_record_id)] }
 
       it 'returns expected error' do
         post_graphql_mutation(mutation, current_user: user)
@@ -159,7 +159,7 @@ def contact_global_ids(contacts)
 
     context 'when trying to remove non-existent contact' do
       let(:operation_mode) { Types::MutationOperationModeEnum.enum[:remove] }
-      let(:contact_ids) { ["gid://gitlab/CustomerRelations::Contact/#{non_existing_record_id}"] }
+      let(:contact_ids) { [global_id_of(model_name: 'CustomerRelations::Contact', id: non_existing_record_id)] }
 
       it 'raises expected error' do
         post_graphql_mutation(mutation, current_user: user)
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index 63b94dccca0cd056d4cfc039ed3f7bc90522a917..dee8f80bc5d9d125690e72a85d847ddd91c3bee6 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -64,7 +64,7 @@ def mutation_response
         it 'creates a Note in a discussion' do
           post_graphql_mutation(mutation, current_user: current_user)
 
-          expect(mutation_response['note']['discussion']['id']).to eq(discussion.to_global_id.to_s)
+          expect(mutation_response['note']['discussion']).to match a_graphql_entity_for(discussion)
         end
 
         context 'when the discussion_id is not for a Discussion' do
diff --git a/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb
index 89e3a71280f6730f6ddd882eec18f8fdd9cb50d3..0f7ccac3179d765fe90ce7d004e57fe91e671776 100644
--- a/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/reposition_image_diff_note_spec.rb
@@ -39,7 +39,7 @@ def mutation_response
       post_graphql_mutation(mutation, current_user: current_user)
     end.to change { note.reset.position.x }.to(10)
 
-    expect(mutation_response['note']).to eq('id' => global_id_of(note))
+    expect(mutation_response['note']).to match a_graphql_entity_for(note)
     expect(mutation_response['errors']).to be_empty
   end
 
@@ -59,7 +59,7 @@ def mutation_response
         post_graphql_mutation(mutation, current_user: current_user)
       end.not_to change { note.reset.position.x }
 
-      expect(mutation_response['note']).to eq('id' => global_id_of(note))
+      expect(mutation_response['note']).to match a_graphql_entity_for(note)
       expect(mutation_response['errors']).to be_empty
     end
   end
diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
index c5c34e167177d0f2df9f4683e367ccdcc03490dd..dc20fde8e3cb4b8fa8053a875f74131c6a4f6373 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
@@ -46,8 +46,8 @@ def mutation_response
     expect(todo3.reload.state).to eq('done')
     expect(other_user_todo.reload.state).to eq('pending')
 
-    updated_todo_ids = mutation_response['todos'].map { |todo| todo['id'] }
-    expect(updated_todo_ids).to contain_exactly(global_id_of(todo1), global_id_of(todo3))
+    updated_todos = mutation_response['todos']
+    expect(updated_todos).to contain_exactly(a_graphql_entity_for(todo1), a_graphql_entity_for(todo3))
   end
 
   context 'when target_id is given', :aggregate_failures do
@@ -66,8 +66,8 @@ def mutation_response
       expect(todo1.reload.state).to eq('pending')
       expect(todo3.reload.state).to eq('pending')
 
-      updated_todo_ids = mutation_response['todos'].map { |todo| todo['id'] }
-      expect(updated_todo_ids).to contain_exactly(global_id_of(target_todo1), global_id_of(target_todo2))
+      updated_todos = mutation_response['todos']
+      expect(updated_todos).to contain_exactly(a_graphql_entity_for(target_todo1), a_graphql_entity_for(target_todo2))
     end
 
     context 'when target does not exist' do
diff --git a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb
index 70e3cc7f5cdc67db9013254fa08032b38e2263f9..4316bd060c175c9d230fe3ba98f4961935fb1bc9 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_many_spec.rb
@@ -11,8 +11,8 @@
   let_it_be(:author) { create(:user) }
   let_it_be(:other_user) { create(:user) }
 
-  let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
-  let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
+  let_it_be_with_reload(:todo1) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
+  let_it_be_with_reload(:todo2) { create(:todo, user: current_user, author: author, state: :done, target: issue) }
 
   let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
 
@@ -50,8 +50,8 @@ def mutation_response
     expect(mutation_response).to include(
       'errors' => be_empty,
       'todos' => contain_exactly(
-        { 'id' => global_id_of(todo1), 'state' => 'pending' },
-        { 'id' => global_id_of(todo2), 'state' => 'pending' }
+        a_graphql_entity_for(todo1, 'state' => 'pending'),
+        a_graphql_entity_for(todo2, 'state' => 'pending')
       )
     )
   end
diff --git a/spec/requests/api/graphql/packages/conan_spec.rb b/spec/requests/api/graphql/packages/conan_spec.rb
index 84c5af33e5d634343769fb865dbb23a7f4b05bf8..1f3732980d90c690e4d78d0501cb1ffb8de4e1ab 100644
--- a/spec/requests/api/graphql/packages/conan_spec.rb
+++ b/spec/requests/api/graphql/packages/conan_spec.rb
@@ -37,22 +37,19 @@
   it_behaves_like 'a package with files'
 
   it 'has the correct metadata' do
-    expect(metadata_response).to include(
-      'id' => global_id_of(package.conan_metadatum),
-      'recipe' => package.conan_metadatum.recipe,
-      'packageChannel' => package.conan_metadatum.package_channel,
-      'packageUsername' => package.conan_metadatum.package_username,
-      'recipePath' => package.conan_metadatum.recipe_path
+    expect(metadata_response).to match(
+      a_graphql_entity_for(package.conan_metadatum,
+                           :recipe, :package_channel, :package_username, :recipe_path)
     )
   end
 
   it 'has the correct file metadata' do
-    expect(first_file_response_metadata).to include(
-      'id' =>  global_id_of(first_file.conan_file_metadatum),
-      'packageRevision' => first_file.conan_file_metadatum.package_revision,
-      'conanPackageReference' => first_file.conan_file_metadatum.conan_package_reference,
-      'recipeRevision' => first_file.conan_file_metadatum.recipe_revision,
-      'conanFileType' => first_file.conan_file_metadatum.conan_file_type.upcase
+    expect(first_file_response_metadata).to match(
+      a_graphql_entity_for(
+        first_file.conan_file_metadatum,
+        :package_revision, :conan_package_reference, :recipe_revision,
+        conan_file_type: first_file.conan_file_metadatum.conan_file_type.upcase
+      )
     )
   end
 end
diff --git a/spec/requests/api/graphql/packages/maven_spec.rb b/spec/requests/api/graphql/packages/maven_spec.rb
index d28d32b0df5e92a3e616e05e21095725bbd5e531..9d59a9226609d27df49ce013f0bb0c9df743b2ac 100644
--- a/spec/requests/api/graphql/packages/maven_spec.rb
+++ b/spec/requests/api/graphql/packages/maven_spec.rb
@@ -11,12 +11,8 @@
 
   shared_examples 'correct maven metadata' do
     it 'has the correct metadata' do
-      expect(metadata_response).to include(
-        'id' => global_id_of(package.maven_metadatum),
-        'path' => package.maven_metadatum.path,
-        'appGroup' => package.maven_metadatum.app_group,
-        'appVersion' => package.maven_metadatum.app_version,
-        'appName' => package.maven_metadatum.app_name
+      expect(metadata_response).to match a_graphql_entity_for(
+        package.maven_metadatum, :path, :app_group, :app_version, :app_name
       )
     end
   end
diff --git a/spec/requests/api/graphql/packages/nuget_spec.rb b/spec/requests/api/graphql/packages/nuget_spec.rb
index ba8d2ca42d22541d714ff9f405a66500847f9441..87cffc67ce56205c6388d11cd0e16520ea33172d 100644
--- a/spec/requests/api/graphql/packages/nuget_spec.rb
+++ b/spec/requests/api/graphql/packages/nuget_spec.rb
@@ -22,24 +22,19 @@
   it_behaves_like 'a package with files'
 
   it 'has the correct metadata' do
-    expect(metadata_response).to include(
-      'id' => global_id_of(package.nuget_metadatum),
-      'licenseUrl' => package.nuget_metadatum.license_url,
-      'projectUrl' => package.nuget_metadatum.project_url,
-      'iconUrl' => package.nuget_metadatum.icon_url
+    expect(metadata_response).to match a_graphql_entity_for(
+      package.nuget_metadatum, :license_url, :project_url, :icon_url
     )
   end
 
   it 'has dependency links' do
-    expect(dependency_link_response).to include(
-      'id' => global_id_of(dependency_link),
+    expect(dependency_link_response).to match a_graphql_entity_for(
+      dependency_link,
       'dependencyType' => dependency_link.dependency_type.upcase
     )
 
-    expect(dependency_response).to include(
-      'id' => global_id_of(dependency_link.dependency),
-      'name' => dependency_link.dependency.name,
-      'versionPattern' => dependency_link.dependency.version_pattern
+    expect(dependency_response).to match a_graphql_entity_for(
+      dependency_link.dependency, :name, :version_pattern
     )
   end
 
diff --git a/spec/requests/api/graphql/packages/pypi_spec.rb b/spec/requests/api/graphql/packages/pypi_spec.rb
index 64fe7d29a7af78bc0fcf19d87081da4bb86a14d2..0cc5bd2e3b2afd07064e70944ff9bd14562d3e8b 100644
--- a/spec/requests/api/graphql/packages/pypi_spec.rb
+++ b/spec/requests/api/graphql/packages/pypi_spec.rb
@@ -19,9 +19,8 @@
   it_behaves_like 'a package with files'
 
   it 'has the correct metadata' do
-    expect(metadata_response).to include(
-      'id' => global_id_of(package.pypi_metadatum),
-      'requiredPython' => package.pypi_metadatum.required_python
+    expect(metadata_response).to match a_graphql_entity_for(
+      package.pypi_metadatum, :required_python
     )
   end
 end
diff --git a/spec/requests/api/graphql/project/alert_management/integrations_spec.rb b/spec/requests/api/graphql/project/alert_management/integrations_spec.rb
index 1793d4961eb3361c8220903cc48dae7ba553dd82..773922c1864d14e4dbc0dc89913ade3fbc73b82d 100644
--- a/spec/requests/api/graphql/project/alert_management/integrations_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/integrations_spec.rb
@@ -53,33 +53,24 @@
       end
 
       context 'when no extra params given' do
-        let(:http_integration_response) { integrations.first }
-        let(:prometheus_integration_response) { integrations.second }
-
         it_behaves_like 'a working graphql query'
 
-        it { expect(integrations.size).to eq(2) }
-
         it 'returns the correct properties of the integrations' do
-          expect(http_integration_response).to include(
-            'id' => global_id_of(active_http_integration),
-            'type' => 'HTTP',
-            'name' => active_http_integration.name,
-            'active' => active_http_integration.active,
-            'token' => active_http_integration.token,
-            'url' => active_http_integration.url,
-            'apiUrl' => nil
-          )
-
-          expect(prometheus_integration_response).to include(
-            'id' => global_id_of(prometheus_integration),
-            'type' => 'PROMETHEUS',
-            'name' => 'Prometheus',
-            'active' => prometheus_integration.manual_configuration?,
-            'token' => project_alerting_setting.token,
-            'url' => "http://localhost/#{project.full_path}/prometheus/alerts/notify.json",
-            'apiUrl' => prometheus_integration.api_url
-          )
+          expect(integrations).to match [
+            a_graphql_entity_for(
+              active_http_integration,
+              :name, :active, :token, :url, type: 'HTTP', api_url: nil
+            ),
+            a_graphql_entity_for(
+              prometheus_integration,
+              'type' => 'PROMETHEUS',
+              'name' => 'Prometheus',
+              'active' => prometheus_integration.manual_configuration?,
+              'token' => project_alerting_setting.token,
+              'url' => "http://localhost/#{project.full_path}/prometheus/alerts/notify.json",
+              'apiUrl' => prometheus_integration.api_url
+            )
+          ]
         end
       end
 
@@ -88,17 +79,9 @@
 
         it_behaves_like 'a working graphql query'
 
-        it { expect(integrations).to be_one }
-
         it 'returns the correct properties of the HTTP integration' do
-          expect(integrations.first).to include(
-            'id' => global_id_of(active_http_integration),
-            'type' => 'HTTP',
-            'name' => active_http_integration.name,
-            'active' => active_http_integration.active,
-            'token' => active_http_integration.token,
-            'url' => active_http_integration.url,
-            'apiUrl' => nil
+          expect(integrations).to contain_exactly a_graphql_entity_for(
+            active_http_integration, :name, :active, :token, :url, type: 'HTTP', api_url: nil
           )
         end
       end
@@ -108,11 +91,9 @@
 
         it_behaves_like 'a working graphql query'
 
-        it { expect(integrations).to be_one }
-
         it 'returns the correct properties of the Prometheus Integration' do
-          expect(integrations.first).to include(
-            'id' => global_id_of(prometheus_integration),
+          expect(integrations).to contain_exactly a_graphql_entity_for(
+            prometheus_integration,
             'type' => 'PROMETHEUS',
             'name' => 'Prometheus',
             'active' => prometheus_integration.manual_configuration?,
diff --git a/spec/requests/api/graphql/project/cluster_agents_spec.rb b/spec/requests/api/graphql/project/cluster_agents_spec.rb
index c9900fea277b045493a484ced832d110657b1d6b..a34df0ee6f4f12236b94910b6ad5585b396c5f3a 100644
--- a/spec/requests/api/graphql/project/cluster_agents_spec.rb
+++ b/spec/requests/api/graphql/project/cluster_agents_spec.rb
@@ -29,7 +29,7 @@
     post_graphql(query, current_user: current_user)
 
     expect(graphql_data_at(:project, :cluster_agents, :nodes)).to match_array(
-      agents.map { |agent| a_hash_including('id' => global_id_of(agent)) }
+      agents.map { |agent| a_graphql_entity_for(agent) }
     )
   end
 
@@ -62,9 +62,9 @@
       tokens = graphql_data_at(:project, :cluster_agents, :nodes, :tokens, :nodes)
 
       expect(tokens).to match([
-        a_hash_including('id' => global_id_of(token_3)),
-        a_hash_including('id' => global_id_of(token_2)),
-        a_hash_including('id' => global_id_of(token_1))
+        a_graphql_entity_for(token_3),
+        a_graphql_entity_for(token_2),
+        a_graphql_entity_for(token_1)
       ])
     end
 
diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
index f544d78ecbb256be85438b6879a895b2eed5c091..8cda61f0628b29c391bff17cef1b377629d6699d 100644
--- a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
+++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
@@ -71,11 +71,7 @@ def query(vq = version_fields)
       it 'finds all the designs as of the given version' do
         post_query
 
-        expect(data).to match(
-          a_hash_including(
-            'id' => global_id_of(design_at_version),
-            'filename' => design.filename
-          ))
+        expect(data).to match a_graphql_entity_for(design_at_version, filename: design.filename)
       end
 
       context 'when the current_user is not authorized' do
@@ -119,7 +115,8 @@ def query(vq = version_fields)
     let(:results) do
       issue.designs.visible_at_version(version).map do |d|
         dav = build(:design_at_version, design: d, version: version)
-        { 'id' => global_id_of(dav), 'filename' => d.filename }
+
+        a_graphql_entity_for(dav, filename: d.filename)
       end
     end
 
@@ -132,8 +129,8 @@ def query(vq = version_fields)
     describe 'filtering' do
       let(:designs) { issue.designs.sample(3) }
       let(:filenames) { designs.map(&:filename) }
-      let(:ids) do
-        designs.map { |d| global_id_of(build(:design_at_version, design: d, version: version)) }
+      let(:expected_designs) do
+        designs.map { |d| a_graphql_entity_for(build(:design_at_version, design: d, version: version)) }
       end
 
       before do
@@ -144,7 +141,7 @@ def query(vq = version_fields)
         let(:dav_params) { { filenames: filenames } }
 
         it 'finds the designs by filename' do
-          expect(data.map { |e| e.dig('node', 'id') }).to match_array(ids)
+          expect(data.map { |e| e['node'] }).to match_array expected_designs
         end
       end
 
@@ -160,9 +157,9 @@ def query(vq = version_fields)
     describe 'pagination' do
       let(:end_cursor) { graphql_data_at(*path_prefix, :designs_at_version, :page_info, :end_cursor) }
 
-      let(:ids) do
+      let(:entities) do
         ::DesignManagement::Design.visible_at_version(version).order(:id).map do |d|
-          global_id_of(build(:design_at_version, design: d, version: version))
+          a_graphql_entity_for(build(:design_at_version, design: d, version: version))
         end
       end
 
@@ -178,19 +175,19 @@ def query(vq = version_fields)
       let(:fields) { ['pageInfo { endCursor }', 'edges { node { id } }'] }
 
       def response_values(data = graphql_data)
-        data.dig(*path).map { |e| e.dig('node', 'id') }
+        data.dig(*path).map { |e| e['node'] }
       end
 
       it 'sorts designs for reliable pagination' do
         post_graphql(query, current_user: current_user)
 
-        expect(response_values).to match_array(ids.take(2))
+        expect(response_values).to match_array(entities.take(2))
 
         post_graphql(cursored_query, current_user: current_user)
 
         new_data = Gitlab::Json.parse(response.body).fetch('data')
 
-        expect(response_values(new_data)).to match_array(ids.drop(2))
+        expect(response_values(new_data)).to match_array(entities.drop(2))
       end
     end
   end
@@ -202,9 +199,7 @@ def response_values(data = graphql_data)
     end
 
     let(:results) do
-      version.designs.map do |design|
-        { 'id' => global_id_of(design), 'filename' => design.filename }
-      end
+      version.designs.map { |design| a_graphql_entity_for(design, :filename) }
     end
 
     it 'finds all the designs as of the given version' do
diff --git a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
index 459a30508ebf3e4f87bc9c90ddc2bb916a98ca40..02bc9457c0710b1b6b276fefdb72f2c2e90b5d6f 100644
--- a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
@@ -58,8 +58,8 @@ def design_image_url(design, ref: nil, size: nil)
 
       post_graphql(query, current_user: current_user)
 
-      expect(design_response).to eq(
-        'id' => design.to_global_id.to_s,
+      expect(design_response).to match a_graphql_entity_for(
+        design,
         'event' => 'CREATION',
         'fullPath' => design.full_path,
         'filename' => design.filename,
@@ -93,7 +93,7 @@ def design_image_url(design, ref: nil, size: nil)
 
       let(:end_cursor) { design_collection.dig('designs', 'pageInfo', 'endCursor') }
 
-      let(:ids) { issue.designs.order(:id).map { |d| global_id_of(d) } }
+      let(:expected_designs) { issue.designs.order(:id).map { |d| a_graphql_entity_for(d) } }
 
       let(:query) { make_query(designs_fragment(first: 2)) }
 
@@ -107,19 +107,19 @@ def designs_fragment(params)
         query_graphql_field(:designs, params, design_query_fields)
       end
 
-      def response_ids(data = graphql_data)
+      def response_designs(data = graphql_data)
         path = %w[project issue designCollection designs edges]
-        data.dig(*path).map { |e| e.dig('node', 'id') }
+        data.dig(*path).map { |e| e['node'] }
       end
 
       it 'sorts designs for reliable pagination' do
-        expect(response_ids).to match_array(ids.take(2))
+        expect(response_designs).to match_array(expected_designs.take(2))
 
         post_graphql(cursored_query, current_user: current_user)
 
         new_data = Gitlab::Json.parse(response.body).fetch('data')
 
-        expect(response_ids(new_data)).to match_array(ids.drop(2))
+        expect(response_designs(new_data)).to match_array(expected_designs.drop(2))
       end
     end
 
diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
index de2ace95757177ce2ef5efd2c740ed4b89db51f2..3b1eb0b4b0254d9e360a360a2bcd430b0a5ac8ef 100644
--- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
@@ -51,7 +51,7 @@ def query(fields)
     design_data = designs_data['nodes'].first
     note_data = design_data['notes']['nodes'].first
 
-    expect(note_data['id']).to eq(note.to_global_id.to_s)
+    expect(note_data).to match(a_graphql_entity_for(note))
   end
 
   def query(note_fields = all_graphql_fields_for(Note, max_depth: 1))
diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb
index ddf63a8f2c962e9cea191ebc995c5c95a3926c97..2415e9ef60f18109a0fa807497dc7c2bdc7aed29 100644
--- a/spec/requests/api/graphql/project/issue_spec.rb
+++ b/spec/requests/api/graphql/project/issue_spec.rb
@@ -144,10 +144,7 @@
 
           data = graphql_data.dig(*path)
 
-          expect(data).to match(
-            a_hash_including('id' => global_id_of(version),
-                             'sha' => version.sha)
-          )
+          expect(data).to match a_graphql_entity_for(version, :sha)
         end
       end
 
@@ -184,6 +181,6 @@
   end
 
   def id_hash(object)
-    a_hash_including('id' => global_id_of(object))
+    a_graphql_entity_for(object)
   end
 end
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index cefe88aafc89bd133674faa07355ef3157a1e742..395b052369e0180b59fc324b367d2de5eedcf48e 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -66,7 +66,7 @@
 
     it 'includes reviewers' do
       expected = merge_request.reviewers.map do |r|
-        a_hash_including('id' => global_id_of(r), 'username' => r.username)
+        a_graphql_entity_for(r, :username)
       end
 
       post_graphql(query, current_user: current_user)
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index 303748bc70ecc170cacfdc06f13df3e40ca96d6f..c7f121c48ab8b8537c15bac6cfc5fe8d3444f07c 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -9,7 +9,7 @@
   let_it_be(:current_user) { create(:user) }
   let_it_be(:label) { create(:label, project: project) }
 
-  let_it_be(:merge_request_a) do
+  let_it_be_with_reload(:merge_request_a) do
     create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label])
   end
 
@@ -412,6 +412,10 @@ def user_collection
   describe 'sorting and pagination' do
     let(:data_path) { [:project, :mergeRequests] }
 
+    def pagination_results_data(nodes)
+      nodes
+    end
+
     def pagination_query(params)
       graphql_query_for(:project, { full_path: project.full_path }, <<~QUERY)
         mergeRequests(#{params}) {
@@ -429,7 +433,7 @@ def pagination_query(params)
           merge_request_c,
           merge_request_e,
           merge_request_a
-        ].map { |mr| global_id_of(mr) }
+        ].map { |mr| a_graphql_entity_for(mr) }
       end
 
       before do
@@ -455,7 +459,7 @@ def pagination_query(params)
           query = pagination_query(params)
           post_graphql(query, current_user: current_user)
 
-          expect(results.map { |item| item["id"] }).to eq(all_records.last(2))
+          expect(results).to match(all_records.last(2))
         end
       end
     end
@@ -469,7 +473,7 @@ def pagination_query(params)
           merge_request_c,
           merge_request_e,
           merge_request_a
-        ].map { |mr| global_id_of(mr) }
+        ].map { |mr| a_graphql_entity_for(mr) }
       end
 
       before do
@@ -495,7 +499,7 @@ def pagination_query(params)
           query = pagination_query(params)
           post_graphql(query, current_user: current_user)
 
-          expect(results.map { |item| item["id"] }).to eq(all_records.last(2))
+          expect(results).to match(all_records.last(2))
         end
       end
     end
diff --git a/spec/requests/api/graphql/project/milestones_spec.rb b/spec/requests/api/graphql/project/milestones_spec.rb
index 2fede4c728537080d77dce6e4c5e2d5a0368b4d6..3e8948d83b18b1cda3960492ae50722e061e1dbb 100644
--- a/spec/requests/api/graphql/project/milestones_spec.rb
+++ b/spec/requests/api/graphql/project/milestones_spec.rb
@@ -33,7 +33,7 @@ def query_milestones(fields)
 
   def result_list(expected)
     expected.map do |milestone|
-      a_hash_including('id' => global_id_of(milestone))
+      a_graphql_entity_for(milestone)
     end
   end
 
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index 73e02e2a4b1214ed0a9104690483a5073820e3ce..ccf97918021f65bcfef73de7c9d68eb96f8814de 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -89,17 +89,16 @@ def successful_pipeline
       post_graphql(query, current_user: current_user)
 
       expect(graphql_data_at(*path, :jobs, :nodes)).to contain_exactly(
-        a_hash_including(
-          'name' => build_job.name,
-          'status' => build_job.status.upcase,
-          'duration' => build_job.duration
+        a_graphql_entity_for(
+          build_job, :name, :duration,
+          'status' => build_job.status.upcase
         ),
-        a_hash_including(
-          'id' => global_id_of(failed_build),
+        a_graphql_entity_for(
+          failed_build,
           'status' => failed_build.status.upcase
         ),
-        a_hash_including(
-          'id' => global_id_of(bridge),
+        a_graphql_entity_for(
+          bridge,
           'status' => bridge.status.upcase
         )
       )
@@ -135,7 +134,7 @@ def successful_pipeline
       post_graphql(query, current_user: current_user, variables: variables)
 
       expect(graphql_data_at(*path, :jobs, :nodes))
-        .to contain_exactly(a_hash_including('id' => global_id_of(failed_build)))
+        .to contain_exactly(a_graphql_entity_for(failed_build))
     end
   end
 
@@ -166,7 +165,7 @@ def successful_pipeline
     end
 
     let(:the_job) do
-      a_hash_including('name' => build_job.name, 'id' => global_id_of(build_job))
+      a_graphql_entity_for(build_job, :name)
     end
 
     it 'can request a build by name' do
diff --git a/spec/requests/api/graphql/project/project_members_spec.rb b/spec/requests/api/graphql/project/project_members_spec.rb
index 315d44884ffc08a001ddd05be309971122edace0..c3281b44954833e68a1759500a3803b920f5e1f3 100644
--- a/spec/requests/api/graphql/project/project_members_spec.rb
+++ b/spec/requests/api/graphql/project/project_members_spec.rb
@@ -60,7 +60,10 @@
         fetch_members(project: parent_project, args: { relations: [:DIRECT] })
 
         expect(graphql_errors).to be_nil
-        expect(graphql_data_at(:project, :project_members, :edges, :node)).to contain_exactly({ 'user' => { 'id' => global_id_of(user) } }, 'user' => nil)
+        expect(graphql_data_at(:project, :project_members, :edges, :node)).to contain_exactly(
+          a_graphql_entity_for(user: a_graphql_entity_for(user)),
+          { 'user' => nil }
+        )
       end
     end
 
@@ -238,7 +241,7 @@ def members_query(group_path, args = {})
 
   def expect_array_response(*items)
     expect(response).to have_gitlab_http_status(:success)
-    member_gids = graphql_data_at(:project, :project_members, :edges, :node, :user, :id)
-    expect(member_gids).to match_array(items.map { |u| global_id_of(u) })
+    members = graphql_data_at(:project, :project_members, :edges, :node, :user)
+    expect(members).to match_array(items.map { |u| a_graphql_entity_for(u) })
   end
 end
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index 77abac4ef04b41e37e0a654ee4e1f910ef6ef179..c4899dbb71e7a8a1142bdac319ea152eaea3ba60 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -77,10 +77,10 @@ def query(rq = release_fields)
         post_query
 
         expected = release.milestones.order_by_dates_and_title.map do |milestone|
-          { 'id' => global_id_of(milestone), 'title' => milestone.title }
+          a_graphql_entity_for(milestone, :title)
         end
 
-        expect(data).to eq(expected)
+        expect(data).to match(expected)
       end
     end
 
@@ -94,10 +94,7 @@ def query(rq = release_fields)
       it 'finds the author of the release' do
         post_query
 
-        expect(data).to eq(
-          'id' => global_id_of(release.author),
-          'username' => release.author.username
-        )
+        expect(data).to match a_graphql_entity_for(release.author, :username)
       end
     end
 
@@ -142,13 +139,11 @@ def query(rq = release_fields)
           post_query
 
           expected = release.links.map do |link|
-            {
-              'id' => global_id_of(link),
-              'name' => link.name,
-              'url' => link.url,
+            a_graphql_entity_for(
+              link, :name, :url,
               'external' => link.external?,
               'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << "/downloads#{link.filepath}" : link.url
-            }
+            )
           end
 
           expect(data).to match_array(expected)
@@ -218,10 +213,8 @@ def query(rq = release_fields)
 
         evidence = release.evidences.first.present
 
-        expect(data["nodes"].first).to eq(
-          'id' => global_id_of(evidence),
-          'sha' => evidence.sha,
-          'filepath' => evidence.filepath,
+        expect(data["nodes"].first).to match a_graphql_entity_for(
+          evidence, :sha, :filepath,
           'collectedAt' => evidence.collected_at.utc.iso8601
         )
       end
@@ -274,10 +267,10 @@ def query(rq = release_fields)
         post_query
 
         expected = release.milestones.order_by_dates_and_title.map do |milestone|
-          { 'id' => global_id_of(milestone), 'title' => milestone.title }
+          a_graphql_entity_for(milestone, :title)
         end
 
-        expect(data).to eq(expected)
+        expect(data).to match(expected)
       end
     end
 
@@ -291,10 +284,7 @@ def query(rq = release_fields)
       it 'finds the author of the release' do
         post_query
 
-        expect(data).to eq(
-          'id' => global_id_of(release.author),
-          'username' => release.author.username
-        )
+        expect(data).to match a_graphql_entity_for(release.author, :username)
       end
     end
 
@@ -339,13 +329,11 @@ def query(rq = release_fields)
           post_query
 
           expected = release.links.map do |link|
-            {
-              'id' => global_id_of(link),
-              'name' => link.name,
-              'url' => link.url,
+            a_graphql_entity_for(
+              link, :name, :url,
               'external' => true,
               'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << "/downloads#{link.filepath}" : link.url
-            }
+            )
           end
 
           expect(data).to match_array(expected)
diff --git a/spec/requests/api/graphql/project/terraform/state_spec.rb b/spec/requests/api/graphql/project/terraform/state_spec.rb
index 9f1d9ab204a89b5d8a03bc797a34e00b7cd40fbe..8f2d2cffef2a77d17d5de3745b3270727f85df98 100644
--- a/spec/requests/api/graphql/project/terraform/state_spec.rb
+++ b/spec/requests/api/graphql/project/terraform/state_spec.rb
@@ -57,22 +57,22 @@
   it_behaves_like 'a working graphql query'
 
   it 'returns terraform state data' do
-    expect(data).to match(a_hash_including({
-      'id'            => global_id_of(terraform_state),
-      'name'          => terraform_state.name,
+    expect(data).to match a_graphql_entity_for(
+      terraform_state,
+      :name,
       'lockedAt'      => terraform_state.locked_at.iso8601,
       'createdAt'     => terraform_state.created_at.iso8601,
       'updatedAt'     => terraform_state.updated_at.iso8601,
-      'lockedByUser'  => { 'id' => global_id_of(terraform_state.locked_by_user) },
-      'latestVersion' => {
-        'id'            => eq(global_id_of(latest_version)),
+      'lockedByUser'  => a_graphql_entity_for(terraform_state.locked_by_user),
+      'latestVersion' => a_graphql_entity_for(
+        latest_version,
         'serial'        => eq(latest_version.version),
         'createdAt'     => eq(latest_version.created_at.iso8601),
         'updatedAt'     => eq(latest_version.updated_at.iso8601),
-        'createdByUser' => { 'id' => eq(global_id_of(latest_version.created_by_user)) },
+        'createdByUser' => a_graphql_entity_for(latest_version.created_by_user),
         'job'           => { 'name' => eq(latest_version.build.name) }
-      }
-    }))
+      )
+    )
   end
 
   context 'unauthorized users' do
diff --git a/spec/requests/api/graphql/project/terraform/states_spec.rb b/spec/requests/api/graphql/project/terraform/states_spec.rb
index 2879530acc5d017afe98afd8d5c7278076dbdf8b..a7ec6f697765746fa596e0ede366cb8d0d97bb7e 100644
--- a/spec/requests/api/graphql/project/terraform/states_spec.rb
+++ b/spec/requests/api/graphql/project/terraform/states_spec.rb
@@ -62,23 +62,22 @@
       )
     )
 
-    expect(data['nodes']).to contain_exactly({
-      'id'            => global_id_of(terraform_state),
-      'name'          => terraform_state.name,
+    expect(data['nodes']).to contain_exactly a_graphql_entity_for(
+      terraform_state, :name,
       'lockedAt'      => terraform_state.locked_at.iso8601,
       'createdAt'     => terraform_state.created_at.iso8601,
       'updatedAt'     => terraform_state.updated_at.iso8601,
-      'lockedByUser'  => { 'id' => global_id_of(terraform_state.locked_by_user) },
-      'latestVersion' => {
-        'id'            => eq(global_id_of(latest_version)),
+      'lockedByUser'  => a_graphql_entity_for(terraform_state.locked_by_user),
+      'latestVersion' => a_graphql_entity_for(
+        latest_version,
         'serial'        => eq(latest_version.version),
         'downloadPath'  => eq(download_path),
         'createdAt'     => eq(latest_version.created_at.iso8601),
         'updatedAt'     => eq(latest_version.updated_at.iso8601),
-        'createdByUser' => { 'id' => eq(global_id_of(latest_version.created_by_user)) },
+        'createdByUser' => a_graphql_entity_for(latest_version.created_by_user),
         'job'           => { 'name' => eq(latest_version.build.name) }
-      }
-    })
+      )
+    )
   end
 
   it 'returns count of terraform states' do
diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb
index d650acc8354e4a495de2657368e1a73c57df2c2e..4aa9c4b825455c670ddbd71c267a0b455b269416 100644
--- a/spec/requests/api/graphql/query_spec.rb
+++ b/spec/requests/api/graphql/query_spec.rb
@@ -76,10 +76,8 @@
       it_behaves_like 'a working graphql query'
       it_behaves_like 'a query that needs authorization'
 
-      context 'the current user is able to read designs' do
-        it 'fetches the expected data' do
-          expect(query_result).to eq('id' => global_id_of(version), 'sha' => version.sha)
-        end
+      it 'fetches the expected data' do
+        expect(query_result).to match a_graphql_entity_for(version, :sha)
       end
     end
 
@@ -106,13 +104,13 @@
 
       context 'the current user is able to read designs' do
         it 'fetches the expected data, including the correct associations' do
-          expect(query_result).to eq(
-            'id' => global_id_of(design_at_version),
+          expect(query_result).to match a_graphql_entity_for(
+            design_at_version,
             'filename' => design_at_version.design.filename,
-            'version' => { 'id' => global_id_of(version), 'sha' => version.sha },
-            'design'  => { 'id' => global_id_of(design) },
+            'version' => a_graphql_entity_for(version, :sha),
+            'design'  => a_graphql_entity_for(design),
             'issue'   => { 'title' => issue.title, 'iid' => issue.iid.to_s },
-            'project' => { 'id' => global_id_of(project), 'fullPath' => project.full_path }
+            'project' => a_graphql_entity_for(project, :full_path)
           )
         end
       end
diff --git a/spec/requests/api/graphql/user/starred_projects_query_spec.rb b/spec/requests/api/graphql/user/starred_projects_query_spec.rb
index a8c087d1fbf0ee5de3c81822cc879387ae0a20d8..37a85b98e5f7bbde39a46e20d0c456aff1587eee 100644
--- a/spec/requests/api/graphql/user/starred_projects_query_spec.rb
+++ b/spec/requests/api/graphql/user/starred_projects_query_spec.rb
@@ -42,7 +42,7 @@
 
   it 'found only public project' do
     expect(starred_projects).to contain_exactly(
-      a_hash_including('id' => global_id_of(project_a))
+      a_graphql_entity_for(project_a)
     )
   end
 
@@ -51,9 +51,9 @@
 
     it 'found all projects' do
       expect(starred_projects).to contain_exactly(
-        a_hash_including('id' => global_id_of(project_a)),
-        a_hash_including('id' => global_id_of(project_b)),
-        a_hash_including('id' => global_id_of(project_c))
+        a_graphql_entity_for(project_a),
+        a_graphql_entity_for(project_b),
+        a_graphql_entity_for(project_c)
       )
     end
   end
@@ -69,8 +69,8 @@
 
     it 'finds public and member projects' do
       expect(starred_projects).to contain_exactly(
-        a_hash_including('id' => global_id_of(project_a)),
-        a_hash_including('id' => global_id_of(project_b))
+        a_graphql_entity_for(project_a),
+        a_graphql_entity_for(project_b)
       )
     end
   end
@@ -93,9 +93,9 @@
 
       it 'finds all projects starred by the user, which the current user has access to' do
         expect(starred_projects).to contain_exactly(
-          a_hash_including('id' => global_id_of(project_a)),
-          a_hash_including('id' => global_id_of(project_b)),
-          a_hash_including('id' => global_id_of(project_c))
+          a_graphql_entity_for(project_a),
+          a_graphql_entity_for(project_b),
+          a_graphql_entity_for(project_c)
         )
       end
     end
diff --git a/spec/requests/api/graphql/user_query_spec.rb b/spec/requests/api/graphql/user_query_spec.rb
index 1cba3674d2571165972e6bfa4ec77333324da445..8f286180617241eaf26a7da4f37d4e405a5facd2 100644
--- a/spec/requests/api/graphql/user_query_spec.rb
+++ b/spec/requests/api/graphql/user_query_spec.rb
@@ -91,11 +91,11 @@
         presenter = UserPresenter.new(user)
 
         expect(graphql_data['user']).to match(
-          a_hash_including(
-            'id' => global_id_of(user),
+          a_graphql_entity_for(
+            user,
+            :username,
             'state' => presenter.state,
             'name' => presenter.name,
-            'username' => presenter.username,
             'webUrl' => presenter.web_url,
             'avatarUrl' => presenter.avatar_url,
             'email' => presenter.public_email,
@@ -121,9 +121,9 @@
 
         it 'can be found' do
           expect(assigned_mrs).to contain_exactly(
-            a_hash_including('id' => global_id_of(assigned_mr)),
-            a_hash_including('id' => global_id_of(assigned_mr_b)),
-            a_hash_including('id' => global_id_of(assigned_mr_c))
+            a_graphql_entity_for(assigned_mr),
+            a_graphql_entity_for(assigned_mr_b),
+            a_graphql_entity_for(assigned_mr_c)
           )
         end
 
@@ -145,7 +145,7 @@
 
             it 'selects the correct MRs' do
               expect(assigned_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(assigned_mr_b))
+                a_graphql_entity_for(assigned_mr_b)
               )
             end
           end
@@ -157,8 +157,8 @@
 
             it 'selects the correct MRs' do
               expect(assigned_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(assigned_mr_b)),
-                a_hash_including('id' => global_id_of(assigned_mr_c))
+                a_graphql_entity_for(assigned_mr_b),
+                a_graphql_entity_for(assigned_mr_c)
               )
             end
           end
@@ -169,7 +169,7 @@
 
             it 'finds the authored mrs' do
               expect(assigned_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(assigned_mr_b))
+                a_graphql_entity_for(assigned_mr_b)
               )
             end
           end
@@ -185,8 +185,8 @@
               post_graphql(query, current_user: current_user)
 
               expect(assigned_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(assigned_mr_b)),
-                a_hash_including('id' => global_id_of(assigned_mr_c))
+                a_graphql_entity_for(assigned_mr_b),
+                a_graphql_entity_for(assigned_mr_c)
               )
             end
           end
@@ -212,9 +212,9 @@
 
         it 'can be found' do
           expect(reviewed_mrs).to contain_exactly(
-            a_hash_including('id' => global_id_of(reviewed_mr)),
-            a_hash_including('id' => global_id_of(reviewed_mr_b)),
-            a_hash_including('id' => global_id_of(reviewed_mr_c))
+            a_graphql_entity_for(reviewed_mr),
+            a_graphql_entity_for(reviewed_mr_b),
+            a_graphql_entity_for(reviewed_mr_c)
           )
         end
 
@@ -236,7 +236,7 @@
 
             it 'selects the correct MRs' do
               expect(reviewed_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(reviewed_mr_b))
+                a_graphql_entity_for(reviewed_mr_b)
               )
             end
           end
@@ -248,8 +248,8 @@
 
             it 'selects the correct MRs' do
               expect(reviewed_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(reviewed_mr_b)),
-                a_hash_including('id' => global_id_of(reviewed_mr_c))
+                a_graphql_entity_for(reviewed_mr_b),
+                a_graphql_entity_for(reviewed_mr_c)
               )
             end
           end
@@ -260,7 +260,7 @@
 
             it 'finds the authored mrs' do
               expect(reviewed_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(reviewed_mr_b))
+                a_graphql_entity_for(reviewed_mr_b)
               )
             end
           end
@@ -275,7 +275,7 @@
               post_graphql(query, current_user: current_user)
 
               expect(reviewed_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(reviewed_mr_c))
+                a_graphql_entity_for(reviewed_mr_c)
               )
             end
           end
@@ -301,9 +301,9 @@
 
         it 'can be found' do
           expect(authored_mrs).to contain_exactly(
-            a_hash_including('id' => global_id_of(authored_mr)),
-            a_hash_including('id' => global_id_of(authored_mr_b)),
-            a_hash_including('id' => global_id_of(authored_mr_c))
+            a_graphql_entity_for(authored_mr),
+            a_graphql_entity_for(authored_mr_b),
+            a_graphql_entity_for(authored_mr_c)
           )
         end
 
@@ -329,8 +329,8 @@
               post_graphql(query, current_user: current_user)
 
               expect(authored_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(authored_mr)),
-                a_hash_including('id' => global_id_of(authored_mr_c))
+                a_graphql_entity_for(authored_mr),
+                a_graphql_entity_for(authored_mr_c)
               )
             end
           end
@@ -346,8 +346,8 @@
               post_graphql(query, current_user: current_user)
 
               expect(authored_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(authored_mr_b)),
-                a_hash_including('id' => global_id_of(authored_mr_c))
+                a_graphql_entity_for(authored_mr_b),
+                a_graphql_entity_for(authored_mr_c)
               )
             end
           end
@@ -359,7 +359,7 @@
 
             it 'selects the correct MRs' do
               expect(authored_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(authored_mr_b))
+                a_graphql_entity_for(authored_mr_b)
               )
             end
           end
@@ -371,8 +371,8 @@
 
             it 'selects the correct MRs' do
               expect(authored_mrs).to contain_exactly(
-                a_hash_including('id' => global_id_of(authored_mr_b)),
-                a_hash_including('id' => global_id_of(authored_mr_c))
+                a_graphql_entity_for(authored_mr_b),
+                a_graphql_entity_for(authored_mr_c)
               )
             end
           end
@@ -417,7 +417,7 @@
 
           it 'can be found' do
             expect(group_memberships).to include(
-              a_hash_including('id' => global_id_of(membership_a))
+              a_graphql_entity_for(membership_a)
             )
           end
         end
@@ -440,7 +440,7 @@
 
           it 'can be found' do
             expect(project_memberships).to include(
-              a_hash_including('id' => global_id_of(membership_a))
+              a_graphql_entity_for(membership_a)
             )
           end
         end
@@ -460,7 +460,7 @@
 
           it 'can be found' do
             expect(authored_mrs).to include(
-              a_hash_including('id' => global_id_of(authored_mr))
+              a_graphql_entity_for(authored_mr)
             )
           end
         end
@@ -480,9 +480,9 @@
 
           it 'can be found' do
             expect(assigned_mrs).to contain_exactly(
-              a_hash_including('id' => global_id_of(assigned_mr)),
-              a_hash_including('id' => global_id_of(assigned_mr_b)),
-              a_hash_including('id' => global_id_of(assigned_mr_c))
+              a_graphql_entity_for(assigned_mr),
+              a_graphql_entity_for(assigned_mr_b),
+              a_graphql_entity_for(assigned_mr_c)
             )
           end
         end
diff --git a/spec/requests/api/graphql/users_spec.rb b/spec/requests/api/graphql/users_spec.rb
index fe824834a2c6de4978f9d483c5bf792cba06d45d..a6bbfc75451b82e7ca88049ed1a11efd56c7f1a4 100644
--- a/spec/requests/api/graphql/users_spec.rb
+++ b/spec/requests/api/graphql/users_spec.rb
@@ -72,12 +72,12 @@
           post_query
 
           expect(graphql_data.dig('users', 'nodes')).to include(
-            { "id" => user0.to_global_id.to_s },
-            { "id" => user1.to_global_id.to_s },
-            { "id" => user2.to_global_id.to_s },
-            { "id" => user3.to_global_id.to_s },
-            { "id" => admin.to_global_id.to_s },
-            { "id" => another_admin.to_global_id.to_s }
+            a_graphql_entity_for(user0),
+            a_graphql_entity_for(user1),
+            a_graphql_entity_for(user2),
+            a_graphql_entity_for(user3),
+            a_graphql_entity_for(admin),
+            a_graphql_entity_for(another_admin)
           )
         end
       end
@@ -91,15 +91,15 @@
           post_graphql(query, current_user: current_user)
 
           expect(graphql_data.dig('users', 'nodes')).to include(
-            { "id" => another_admin.to_global_id.to_s },
-            { "id" => admin.to_global_id.to_s }
+            a_graphql_entity_for(another_admin),
+            a_graphql_entity_for(admin)
           )
 
           expect(graphql_data.dig('users', 'nodes')).not_to include(
-            { "id" => user0.to_global_id.to_s },
-            { "id" => user1.to_global_id.to_s },
-            { "id" => user2.to_global_id.to_s },
-            { "id" => user3.to_global_id.to_s }
+            a_graphql_entity_for(user0),
+            a_graphql_entity_for(user1),
+            a_graphql_entity_for(user2),
+            a_graphql_entity_for(user3)
           )
         end
       end
diff --git a/spec/support/graphql/arguments.rb b/spec/support/graphql/arguments.rb
index 20e940030f8250e0578a1f4db82ec955bf138e48..a5bb01c31a3cf76cc5185bbd0c2570ba237588a8 100644
--- a/spec/support/graphql/arguments.rb
+++ b/spec/support/graphql/arguments.rb
@@ -40,7 +40,7 @@ def self.as_graphql_literal(value)
       when Array then "[#{value.map { |v| as_graphql_literal(v) }.join(',')}]"
       when Hash then "{#{new(value)}}"
       when Integer, Float, Symbol then value.to_s
-      when String then "\"#{value.gsub(/"/, '\\"')}\""
+      when String, GlobalID then "\"#{value.to_s.gsub(/"/, '\\"')}\""
       when Time, Date then "\"#{value.iso8601}\""
       when nil then 'null'
       when true then 'true'
@@ -49,7 +49,7 @@ def self.as_graphql_literal(value)
         value.to_graphql_value
       end
     rescue NoMethodError
-      raise ArgumentError, "Cannot represent #{value} as GraphQL literal"
+      raise ArgumentError, "Cannot represent #{value} (instance of #{value.class}) as GraphQL literal"
     end
 
     def merge(other)
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 652cc51fcc02df1b96751a43a8540648567895b2..eb0e5a2573386213d432825a53a4a7bc7602e14d 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -244,6 +244,7 @@ def wrap_query(query)
   def graphql_mutation(name, input, fields = nil, &block)
     raise ArgumentError, 'Please pass either `fields` parameter or a block to `#graphql_mutation`, but not both.' if fields.present? && block_given?
 
+    name = name.graphql_name if name.respond_to?(:graphql_name)
     mutation_name = GraphqlHelpers.fieldnamerize(name)
     input_variable_name = "$#{input_variable_name_for_mutation(name)}"
     mutation_field = GitlabSchema.mutation.fields[mutation_name]
@@ -264,7 +265,7 @@ def graphql_mutation(name, input, fields = nil, &block)
   end
 
   def variables_for_mutation(name, input)
-    graphql_input = prepare_input_for_mutation(input)
+    graphql_input = prepare_variables(input)
 
     { input_variable_name_for_mutation(name) => graphql_input }
   end
@@ -273,18 +274,28 @@ def serialize_variables(variables)
     return unless variables
     return variables if variables.is_a?(String)
 
-    ::Gitlab::Utils::MergeHash.merge(Array.wrap(variables).map(&:to_h)).to_json
+    # Combine variables into a single hash.
+    hash = ::Gitlab::Utils::MergeHash.merge(Array.wrap(variables).map(&:to_h))
+
+    prepare_variables(hash).to_json
   end
 
-  # Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys
+  # Recursively convert any ruby object we can pass as a variable value
+  # to an object we can serialize with JSON, using fieldname-style keys
   #
-  # prepare_input_for_mutation({ 'my_key' => 1 })
-  #   => { 'myKey' => 1}
-  def prepare_input_for_mutation(input)
-    input.to_h do |name, value|
-      value = prepare_input_for_mutation(value) if value.is_a?(Hash)
+  # prepare_variables({ 'my_key' => 1 })
+  #   => { 'myKey' => 1 }
+  # prepare_variables({ enums: [:FOO, :BAR], user_id: global_id_of(user) })
+  #   => { 'enums' => ['FOO', 'BAR'], 'userId' => "gid://User/123" }
+  # prepare_variables({ nested: { hash_values: { are_supported: true } } })
+  #   => { 'nested' => { 'hashValues' => { 'areSupported' => true } } }
+  def prepare_variables(input)
+    return input.map { prepare_variables(_1) } if input.is_a?(Array)
+    return input.to_s if input.is_a?(GlobalID) || input.is_a?(Symbol)
+    return input unless input.is_a?(Hash)
 
-      [GraphqlHelpers.fieldnamerize(name), value]
+    input.to_h do |name, value|
+      [GraphqlHelpers.fieldnamerize(name), prepare_variables(value)]
     end
   end
 
@@ -650,9 +661,9 @@ def node_array(data, extract_attribute = nil)
     end
   end
 
-  def global_id_of(model, id: nil, model_name: nil)
+  def global_id_of(model = nil, id: nil, model_name: nil)
     if id || model_name
-      ::Gitlab::GlobalId.build(model, id: id, model_name: model_name).to_s
+      ::Gitlab::GlobalId.as_global_id(id || model.id, model_name: model_name || model.class.name).to_s
     else
       model.to_global_id.to_s
     end
@@ -714,6 +725,67 @@ def empty_schema
     end
   end
 
+  # Wrapper around a_hash_including that supports unpacking with **
+  class UnpackableMatcher < SimpleDelegator
+    include RSpec::Matchers
+
+    attr_reader :to_hash
+
+    def initialize(hash)
+      @to_hash = hash
+      super(a_hash_including(hash))
+    end
+
+    def to_json(_opts = {})
+      to_hash.to_json
+    end
+
+    def as_json(opts = {})
+      to_hash.as_json(opts)
+    end
+  end
+
+  # Construct a matcher for GraphQL entity response objects, of the form
+  # `{ "id" => "some-gid" }`.
+  #
+  # Usage:
+  #
+  # ```ruby
+  # expect(graphql_data_at(:path, :to, :entity)).to match a_graphql_entity_for(user)
+  # ```
+  #
+  # This can be called as:
+  #
+  # ```ruby
+  # a_graphql_entity_for(project, :full_path) # also checks that `entity['fullPath'] == project.full_path
+  # a_graphql_entity_for(project, full_path: 'some/path') # same as above, with explicit values
+  # a_graphql_entity_for(user, :username, foo: 'bar') # combinations of the above
+  # a_graphql_entity_for(foo: 'bar') # if properties are defined, the model is not necessary
+  # ```
+  #
+  # Note that the model instance must not be nil, unless some properties are
+  # explicitly passed in. The following are rejected with `ArgumentError`:
+  #
+  # ```
+  # a_graphql_entity_for(nil, :username)
+  # a_graphql_entity_for(:username)
+  # a_graphql_entity_for
+  # ```
+  #
+  def a_graphql_entity_for(model = nil, *fields, **attrs)
+    raise ArgumentError, 'model is nil' if model.nil? && fields.any?
+
+    attrs.transform_keys! { GraphqlHelpers.fieldnamerize(_1) }
+    attrs['id'] = global_id_of(model) if model
+    fields.each do |name|
+      attrs[GraphqlHelpers.fieldnamerize(name)] = model.public_send(name)
+    end
+
+    raise ArgumentError, 'no attributes' if attrs.empty?
+
+    UnpackableMatcher.new(attrs)
+  end
+
   # A lookahead that selects everything
   def positive_lookahead
     double(selects?: true).tap do |selection|
diff --git a/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb b/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb
index 13e7ecf2669850f2ad349dbdb2e0d77f5576f08d..b29a231f3a6fbd79f32db7f922d1df462b29de96 100644
--- a/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb
+++ b/spec/support/shared_contexts/graphql/requests/packages_shared_context.rb
@@ -14,7 +14,7 @@
   let(:user) { project.first_owner }
   let(:package_details) { graphql_data_at(:package) }
   let(:metadata_response) { graphql_data_at(:package, :metadata) }
-  let(:first_file) { package.package_files.find { |f| global_id_of(f) == first_file_response['id'] } }
+  let(:first_file) { package.package_files.find { |f| a_graphql_entity_for(f).matches?(first_file_response) } }
   let(:package_files_response) { graphql_data_at(:package, :package_files, :nodes) }
   let(:first_file_response) { graphql_data_at(:package, :package_files, :nodes, 0)}
   let(:first_file_response_metadata) { graphql_data_at(:package, :package_files, :nodes, 0, :file_metadata)}
diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
index 37a805902a9aa9d187c0aa3f92c98864d6bbe28f..6d6e7b761f6c893753e3c53a1f81e16cf250329d 100644
--- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb
@@ -101,7 +101,7 @@ def start_cursor
 
     context 'when sorting' do
       it 'sorts correctly' do
-        expect(results).to eq all_records
+        expect(results).to match all_records
       end
 
       context 'when paginating' do
@@ -110,17 +110,17 @@ def start_cursor
         let(:rest) { all_records.drop(first_param) }
 
         it 'paginates correctly' do
-          expect(results).to eq first_page
+          expect(results).to match first_page
 
           fwds = pagination_query(sort_argument.merge(after: end_cursor))
           post_graphql(fwds, current_user: current_user)
 
-          expect(results).to eq rest
+          expect(results).to match rest
 
           bwds = pagination_query(sort_argument.merge(before: start_cursor))
           post_graphql(bwds, current_user: current_user)
 
-          expect(results).to eq first_page
+          expect(results).to match first_page
         end
       end
 
@@ -130,7 +130,7 @@ def start_cursor
         it 'fetches last elements without error' do
           post_graphql(pagination_query(params), current_user: current_user)
 
-          expect(results.first).to eq(all_records.last)
+          expect(results.first).to match all_records.last
         end
       end
     end
diff --git a/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
index 7e1f45007790c7b73d1f02fe4f25c7ac0e0b8685..31f2519a132bae0378fb1dac3ec1149c5c3b1a85 100644
--- a/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/noteable_shared_examples.rb
@@ -12,8 +12,8 @@
 
     def expected
       noteable.discussions.map do |discussion|
-        include(
-          'id' => global_id_of(discussion),
+        a_graphql_entity_for(
+          discussion,
           'replyId' => global_id_of(discussion, id: discussion.reply_id),
           'createdAt' => discussion.created_at.iso8601,
           'notes' => include(
@@ -50,8 +50,8 @@ def expected
 
       post_graphql(query(fields), current_user: current_user)
 
-      data = graphql_data_at(*path_to_noteable, :discussions, :nodes, :noteable, :id)
-      expect(data[0]).to eq(global_id_of(noteable))
+      entities = graphql_data_at(*path_to_noteable, :discussions, :nodes, :noteable)
+      expect(entities).to all(match(a_graphql_entity_for(noteable)))
     end
   end
 
@@ -62,10 +62,10 @@ def expected
 
     def expected
       noteable.notes.map do |note|
-        include(
-          'id' => global_id_of(note),
-          'project' => include('id' => global_id_of(project)),
-          'author' => include('id' => global_id_of(note.author)),
+        a_graphql_entity_for(
+          note,
+          'project' => a_graphql_entity_for(project),
+          'author' => a_graphql_entity_for(note.author),
           'createdAt' => note.created_at.iso8601,
           'body' => eq(note.note)
         )
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index 127b1a6d4c40afa1578c2320d959c7054cf5ae27..4c1a443eb4d44fb5d03c169603dd5488f8c880ff 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -104,7 +104,7 @@
       }
     end
 
-    let(:expected_packages) { sorted_packages.map { |package| global_id_of(package) } }
+    let(:expected_packages) { sorted_packages.map { |package| global_id_of(package).to_s } }
 
     let(:data_path) { [resource_type, :packages] }
 
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
index ab93f54111b295817c2f54c0ba5796f713adec7a..b4019d7c232a8a58a819ad16f26dc4444136501e 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb
@@ -28,14 +28,10 @@
   end
 
   it 'has the basic package files data' do
-    expect(first_file_response).to include(
-      'id' => global_id_of(first_file),
-      'fileName' => first_file.file_name,
-      'size' => first_file.size.to_s,
-      'downloadPath' => first_file.download_path,
-      'fileSha1' => first_file.file_sha1,
-      'fileMd5' => first_file.file_md5,
-      'fileSha256' => first_file.file_sha256
+    expect(first_file_response).to match a_graphql_entity_for(
+      first_file,
+      :file_name, :download_path, :file_sha1, :file_md5, :file_sha256,
+      'size' => first_file.size.to_s
     )
   end
 
diff --git a/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
index c134f7d1839fd7691416eb26862c2896d7c2a476..3c5f25baaa1d78e4bad6a6475bd9e10351fbc626 100644
--- a/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/projects/alert_management/integrations_shared_examples.rb
@@ -30,14 +30,12 @@
     it 'returns the correct properties of the integrations', :aggregate_failures do
       post_graphql(multi_selection_query, current_user: current_user)
 
-      expect(graphql_data.dig('project', 'ai', 'nodes')).to include(
-        'id' => global_id_of(active_http_integration),
-        'name' => active_http_integration.name
+      expect(graphql_data.dig('project', 'ai', 'nodes')).to match a_graphql_entity_for(
+        active_http_integration, :name
       )
 
-      expect(graphql_data.dig('project', 'ii', 'nodes')).to include(
-        'id' => global_id_of(inactive_http_integration),
-        'name' => inactive_http_integration.name
+      expect(graphql_data.dig('project', 'ii', 'nodes')).to match a_graphql_entity_for(
+        inactive_http_integration, :name
       )
     end
 
diff --git a/spec/support_specs/helpers/graphql_helpers_spec.rb b/spec/support_specs/helpers/graphql_helpers_spec.rb
index fae29ec32f53238ea4473b1d7d4dd7f474053995..0f9918351e2cf79e8a772553eb9ca55beea422e8 100644
--- a/spec/support_specs/helpers/graphql_helpers_spec.rb
+++ b/spec/support_specs/helpers/graphql_helpers_spec.rb
@@ -10,6 +10,81 @@ def norm(query)
     query.tr("\n", ' ').gsub(/\s+/, ' ').strip
   end
 
+  describe 'a_graphql_entity_for' do
+    context 'when no arguments are passed' do
+      it 'raises an error' do
+        expect { a_graphql_entity_for }.to raise_error(ArgumentError)
+      end
+    end
+
+    context 'when the model is nil, with no properties' do
+      it 'raises an error' do
+        expect { a_graphql_entity_for(nil) }.to raise_error(ArgumentError)
+      end
+    end
+
+    context 'when the model is nil, any fields are passed' do
+      it 'raises an error' do
+        expect { a_graphql_entity_for(nil, :username) }.to raise_error(ArgumentError)
+      end
+    end
+
+    context 'with no model' do
+      it 'behaves like hash-inclusion with camel-casing' do
+        response = { 'foo' => 1, 'bar' => 2, 'camelCased' => 3 }
+
+        expect(response).to match a_graphql_entity_for(foo: 1, camel_cased: 3)
+        expect(response).not_to match a_graphql_entity_for(missing: 5)
+      end
+    end
+
+    context 'with just a model' do
+      it 'only considers the ID' do
+        user = build_stubbed(:user)
+        response = { 'username' => 'foo', 'id' => global_id_of(user) }
+
+        expect(response).to match a_graphql_entity_for(user)
+      end
+    end
+
+    context 'with a model and some method names' do
+      it 'also considers the method names' do
+        user = build_stubbed(:user)
+        response = { 'username' => user.username, 'id' => global_id_of(user) }
+
+        expect(response).to match a_graphql_entity_for(user, :username)
+        expect(response).not_to match a_graphql_entity_for(user, :name)
+      end
+    end
+
+    context 'with a model and some other properties' do
+      it 'behaves like the superset' do
+        user = build_stubbed(:user)
+        response = { 'username' => 'foo', 'id' => global_id_of(user) }
+
+        expect(response).to match a_graphql_entity_for(user, username: 'foo')
+        expect(response).not_to match a_graphql_entity_for(user, name: 'foo')
+      end
+    end
+
+    context 'with a model, method names, and some other properties' do
+      it 'behaves like the superset' do
+        user = build_stubbed(:user)
+        response = {
+          'username' => user.username,
+          'name' => user.name,
+          'foo' => 'bar',
+          'baz' => 'fop',
+          'id' => global_id_of(user)
+        }
+
+        expect(response).to match a_graphql_entity_for(user, :username, :name, foo: 'bar')
+        expect(response).to match a_graphql_entity_for(user, :name, foo: 'bar')
+        expect(response).not_to match a_graphql_entity_for(user, :name, bar: 'foo')
+      end
+    end
+  end
+
   describe 'graphql_dig_at' do
     it 'transforms symbol keys to graphql field names' do
       data = { 'camelCased' => 'names' }