YouTag
YouTag copied to clipboard
Comparing two GraphQLResult
Problem
Currently can't compare two GraphQLResult even though the data is the same.
I'm currently using swift-testing with Swift 6.0.2
let queryOrganizationBySlug: String = """
query FindSingleOrganization($slug: String!) {
findSingleOrganization(slug: $slug) {
slug
displayName
details
domain
}
}
"""
let variables: [String: Map] = ["slug": "acme-corp"]
let graphQLRequest = GraphQLRequest(query: query, variables: variables)
let encoder = GraphQLJSONEncoder()
let buffer = ByteBuffer(data: try encoder.encode(graphQLRequest))
let response: GraphQLResult = try await client.runGraphQLQuery(
url: "/graphql",
buffer: buffer
)
let expected: GraphQLResult = GraphQLResult(
data: [
"findSingleOrganization": [
"slug": "acme-corp",
"displayName": "Acme Corporation",
"details": "A leading technology company",
"domain": "acme.com"
]
]
)
#expect(response == expected)
However I'm getting an issue on comparison even though GraphQLResult is Equatable. The error is as follows:
✘ Expectation failed: (response → {"data":{"findSingleOrganization":{"displayName":"Acme Corporation","details":"A leading technology company","domain":"acme.com","slug":"acme-corp"}}}) == (expected → {"data":{"findSingleOrganization":{"slug":"acme-corp","displayName":"Acme Corporation","details":"A leading technology company","domain":"acme.com"}}})
Which we can see that the data is the same, the only thing is that the data is in differing orders:
[
{
"data": {
"findSingleOrganization": {
"details": "A leading technology company",
"domain": "acme.com",
"slug": "acme-corp",
"displayName": "Acme Corporation"
}
}
},
{
"data": {
"findSingleOrganization": {
"slug": "acme-corp",
"displayName": "Acme Corporation",
"details": "A leading technology company",
"domain": "acme.com"
}
}
}
]
What's the proper way to compare the results?
In addition, I used
let graphQLRequest = GraphQLRequest(query: query, variables: variables)
let encoder = GraphQLJSONEncoder()
It looks like you were doing the right thing using GraphQLJSONEncoder. Do you happen to know what Decoder client.runGraphQLQuery is using under the hood to convert to a GraphQLResult?
For background, JSON doesn't care about ordering, but GraphQL does, so we've had to do quite a bit of work to get GraphQL to play nice, and may have missed a spot on decoding.
As an alternative, you could check each field (this is psudocode - you might have to fix it up):
let expectedDictionary = expected.data?.dictionary?["findSingleOrganization"].dictionary!
for expectedField, expectedValue in expectedDictionary {
#expect(expectedValue == response.data?.dictionary?["findSingleOrganization"].dictionary?[expectedField])
}
It's not pretty, but it should do the trick.
Apologies for the late reply, was traveling for the holidays.
The following is the swift extension that I wrote for my Integration testing framework.
import Foundation
import GraphQL
import HTTPTypes
import Hummingbird
import HummingbirdTesting
extension TestClientProtocol {
/// Run a GraphQL query and return the result.
///
/// - Parameters:
/// - query: The GraphQL query to run.
/// - variables: The variables to pass to the query.
/// - client: The client to use to run the query.
/// - testUserId: The user ID to use for authentication.
/// - file: The file to use for logging.
/// - line: The line to use for logging.
///
/// - Returns: The result of the GraphQL query.
func runGraphQLQuery(
_ query: String,
variables: [String: Map] = [:],
testUserId: String,
file: StaticString = #filePath,
line: UInt = #line
) async throws -> GraphQLResult {
var headers: HTTPFields = HTTPFields()
headers[HTTPField.Name("Authorization")!] = "Bearer \(testUserId)"
headers[HTTPField.Name("Content-Type")!] = "application/json; charset=utf-8"
let graphQLRequest = GraphQLRequest(query: query, variables: variables)
let encoder = GraphQLJSONEncoder()
let buffer = ByteBuffer(data: try encoder.encode(graphQLRequest))
let response = try await self.execute(uri: "/graphql", method: .post, headers: headers, body: buffer)
let decoder = JSONDecoder()
let graphQLResult: GraphQLResult = try decoder.decode(GraphQLResult.self, from: response.body)
return graphQLResult
}
}
Oh okay, that makes sense.
For background, GraphQLResult.data uses the Map structure, which itself uses an OrderedDictionary of fields. This is important in order to deliver the correct ordering of results in the JSON response, which is required by the graphQL spec. However, it means that Map equality requires consistent ordering of the fields.
The issue here is that JSONDecoder internally uses an ordinary Dictionary, which does not preserve ordering, so your decoded GraphQLResult has non-deterministic ordering of its fields, which may not match your declared expected GraphQLResult
I'd suggest that you instead model the expected response as a standard decodable type and don't use GraphQLResult which will avoid any ordering considerations. For example:
struct FindSingleOrganizationResponse: Codable {
let data: DataResponse
struct DataResponse: Codable {
let findSingleOrganization: [Organization]
struct Organization: Codable {
let slug: String
let displayName: String
let details: String
let domain: String
}
}
}
let expected = FindSingleOrganizationResponse(
data: .init(
findSingleOrganization: [.init(
slug: "acme-corp",
displayName: "Acme Corporation",
details: "A leading technology company",
domain: "acme.com"
)]
)
)
... // Get response
let actual = try JSONDecoder().decode(FindSingleOrganizationResponse.self, from: response.body)
#expect(actual == expected)
Just be aware: I didn't test the code above, so you might have to fiddle with it a bit.