Apollo-ios: How to create Query.Data without getting it through the AppoloClient?

Created on 6 Feb 2017  路  14Comments  路  Source: apollographql/apollo-ios

With Apollo Codegen an API.swift file is generate with contains the data structs.
But how can you create a struct without getting it through the AppoloClient as there is only an init which accepts a GraphQLResultReader?
I'm looking to create the data in a unit testing scenario without having to get it through the AppoloClient.
I tried creating a mock AppoloStore or a GraphQLResultReader but that doesn't seem possible as the init or other methods are inaccessible.

Take for example following API.swift file.
How would you create a Channel?

//  This file was automatically generated and should not be edited.

import Apollo

public final class ProofOfConceptQuery: GraphQLQuery {
  public static let operationDefinition =
    "query ProofOfConcept($profileId: ID!) {" +
    "  initialChannelList(profileId: $profileId) {" +
    "    id" +
    "    title" +
    "    channels {" +
    "      id" +
    "      title" +
    "    }" +
    "  }" +
    "}"

  public let profileId: GraphQLID

  public init(profileId: GraphQLID) {
    self.profileId = profileId
  }

  public var variables: GraphQLMap? {
    return ["profileId": profileId]
  }

  public struct Data: GraphQLMappable {
    public let initialChannelList: InitialChannelList?

    public init(reader: GraphQLResultReader) throws {
      initialChannelList = try reader.optionalValue(for: Field(responseName: "initialChannelList", arguments: ["profileId": reader.variables["profileId"]]))
    }

    public struct InitialChannelList: GraphQLMappable {
      public let __typename = "ChannelList"
      public let id: GraphQLID
      public let title: String
      public let channels: [Channel?]

      public init(reader: GraphQLResultReader) throws {
        id = try reader.value(for: Field(responseName: "id"))
        title = try reader.value(for: Field(responseName: "title"))
        channels = try reader.list(for: Field(responseName: "channels"))
      }

      public struct Channel: GraphQLMappable {
        public let __typename = "Channel"
        public let id: GraphQLID
        public let title: String

        public init(reader: GraphQLResultReader) throws {
          id = try reader.value(for: Field(responseName: "id"))
          title = try reader.value(for: Field(responseName: "title"))
        }
      }
    }
  }
}

Most helpful comment

My bad, it didn't show up in the autocompletion. Thanks!

All 14 comments

I'd be interested in hearing more about how people intend to use query results with tests, and see how we can make this easier. It may make sense to also generate memberwise constructors for these structs.

For now, the easiest way is probably to create a GraphQLResponse object and use parseResult() to create a Query.Data struct. See this test case for an example.

Thanks for the quick response.
I tried using what you suggested but it doesn't seem possible.

'parseResult' is inaccessible due to 'internal' protection level

And yes generating memberwise constructors would be much more convenient than having to parse json to create a struct.

@Nuke-: Make sure to update to the most recent version, because parseResult was only made public today.

That made it work. Thanks.

I'm also having the error saying that parseResult is inaccessible. Is there been an other way to get data using a local json file?

import Foundation
import Apollo

class MyGraphQL {

    func getInbox() {
        guard let rootObject = json(forFileName: "inbox") else { return }
        let query = InboxQuery()
        let response = GraphQLResponse(operation: query, body: rootObject)

        let (result, _) = try response.parseResult().await()
    }

    private func json(forFileName fileName: String) -> JSONObject? {
        guard let file = Bundle.main.path(forResource: fileName, ofType: "json"),
            let string = try? String(contentsOfFile: file, encoding: .utf8),
            let data = string.data(using: .utf8),
            let object = try? JSONSerialization.jsonObject(with: data, options: []) else {
                return nil
        }
        return object as? JSONObject
    }
}

@jbouaziz: You should be able to use InboxQuery.Data(jsonObject:) for that.

It works thanks @martijnwalraven.

For those who come here, the exact method name is InboxQuery.Data(snapshot: your_json).

@jbouaziz: No, the snapshot constructor is not actually meant to be public! It doesn't do any type checking / conversion and can lead to crashes. So you should use InboxQuery.Data(jsonObject:) instead.

My bad, it didn't show up in the autocompletion. Thanks!

Hi, Im having issues like "Auto-Linking framework not found Apollo" when I try to use Data(jsonObject:)
screen_shot_2018-05-04_at_12_04_53_pm

I have googled it and not answer for this...

I'm using 0.8 version and parseResult() still isn't public. I had to unlock the Pod and turn it into public to be able to test. Is there another way? GraphQLResponse is a struct and it's init method isn't public as well. Thanks!

@marinofelipe You shouldn't have to use parseResult for that, you can use Query.Data(jsonObject:) to create the data and use that to initialize GraphQLResult if that's what you need.

@martijnwalraven Thanks for your fast reply. I can't initialise GraphQLResult due to internal level protection.

If this protection have to exist, what do you think about making GraphQLResult adopt a protocol? This way we would have an interface to give us more flexibility.

Was this page helpful?
0 / 5 - 0 ratings