Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In 1.1.0, passing custom scalars or GraphQLEnum to mocks fails #2928

Closed
vincentisambart opened this issue Apr 4, 2023 · 12 comments · Fixed by #2939
Closed

In 1.1.0, passing custom scalars or GraphQLEnum to mocks fails #2928

vincentisambart opened this issue Apr 4, 2023 · 12 comments · Fixed by #2939
Assignees
Labels
bug Generally incorrect behavior needs investigation planned-next Slated to be included in the next release

Comments

@vincentisambart
Copy link
Contributor

vincentisambart commented Apr 4, 2023

Summary

In Apollo iOS 1.1.0, when you pass enum or custom scalar values to create test mocks, you get a JSON decoding error:

ApolloTestSupport/TestMock.swift:93: Fatal error: 'try!' expression unexpectedly raised an error: Apollo.GraphQLExecutionError(path: topFrameContent.align, underlying: ApolloAPI.JSONDecodingError.couldNotConvert(value: AnyHashable(ApolloAPI.GraphQLEnum<Api4.BannerAlign>.case(Api4.BannerAlign.center)), to: Swift.String))

For custom scalars, I could work around this problem by modifying their init(_jsonValue value: JSONValue) throws to allow value being of their own type(if let myCustomScalar = value as? MyCustomScalar), but I cannot do that to ApolloAPI.GraphQLEnum.

At first thought it was me incorrectly use the Apollo iOS API, but the convenience init is expecting concrete values of the custom scalars and GraphQLEnum, so I looks like a bug to me.

Version

1.1.0

Steps to reproduce the behavior

You need a type that has fields with enums and/or custom scalars.

enum Alignment {
  LEFT
  CENTER
  RIGHT
} 
type Banner {
  alignment: Alignment
}

And in Swift the following test code will work on Apollo iOS 1.0 but will end up with a JSONDecodingError in 1.1.

let mock = Mock<Banner>(alignment: GraphQLEnum(Banner.center))

Sorry I don't have the time to make a full reproduction project so the code above was not tested, but should fail seeing the behavior on our project.

Logs

No response

Anything else?

No response

@vincentisambart vincentisambart added bug Generally incorrect behavior needs investigation labels Apr 4, 2023
@AnthonyMDev AnthonyMDev added this to the Patch Releases (1.1.x) milestone Apr 4, 2023
@AnthonyMDev AnthonyMDev added the planned-next Slated to be included in the next release label Apr 4, 2023
@AnthonyMDev AnthonyMDev self-assigned this Apr 4, 2023
@AnthonyMDev
Copy link
Contributor

Thanks so much for the report! I'll look into this and get a fix out!

@AnthonyMDev
Copy link
Contributor

I've got a unit test reproducing this now and I'm working on a fix!

@vincentisambart
Copy link
Contributor Author

Thank you for the quick fix!

@alexjameslittle
Copy link

We are still having this issue on the latest version 1.1.2.

couldNotConvert(value: AnyHashable(ApolloAPI.GraphQLEnum<ProfileFriendStatus>.case(ProfileFriendStatus.none)), to: Swift.String)

@RussellToon
Copy link

And I still seem to have this issue in 1.1.3

@calvincestari
Copy link
Member

@alexjameslittle, @RussellToon - it would help if either of you are able to provide a sample application that demonstrates the issue you're still experiencing.

@alexjameslittle
Copy link

I'm not sure I have the project available to provide an example anymore, however we added this based on some inspiration from this repo and it solved our issues. We now no longer have these issues

@testable import Apollo
@testable import ApolloAPI
@testable import ApolloTestSupport

public extension RootSelectionSet {
    static func from(query: Mock<Query>) throws -> Self {
        try .init(data: query._selectionSetMockData)
    }

    static func from(subscription: Mock<Subscription>) throws -> Self {
        try .init(data: subscription._selectionSetMockData)
    }

    static func from(mutation: Mock<Mutation>) throws -> Self {
        try .init(data: mutation._selectionSetMockData)
    }
}

public extension RootSelectionSet {
    init(
        data: JSONObject,
        variables: GraphQLOperation.Variables? = nil
    ) throws {
        let accumulator = TestMockSelectionSetMapper<Self>()
        let executor = GraphQLExecutor { object, info in
            object[info.responseKeyForField]
        }
        executor.shouldComputeCachePath = false

        self = try executor.execute(
            selectionSet: Self.self,
            on: data,
            variables: variables,
            accumulator: accumulator
        )
    }
}

@AnthonyMDev
Copy link
Contributor

Thanks for the code example! I'm not sure what problem you are having that this code resolves for you, since that is basically what we are doing already in the function we have for this.

FWIW, the GraphQLExecutor was changed significantly in 1.2.0. Curious to know if upgrading to 1.2.0 resolves these crashes for you. (Though 1.2 currently has a few other known bugs we're working on resolving that may prevent you from upgrading at the moment in some situations)

@alexjameslittle
Copy link

I just managed to recreate it again on our current version 1.1.2. I haven't had the chance to test on any newer version yet as it's a large company project.

This is the root selection set extension that gets called in our test if we don't use the extension i linked:

extension RootSelectionSet {

  /// Initializes a `SelectionSet` with a raw JSON response object.
  ///
  /// The process of converting a JSON response into `SelectionSetData` is done by using a
  /// `GraphQLExecutor` with a`GraphQLSelectionSetMapper` to parse, validate, and transform
  /// the JSON response data into the format expected by `SelectionSet`.
  ///
  /// - Parameters:
  ///   - data: A dictionary representing a JSON response object for a GraphQL object.
  ///   - variables: [Optional] The operation variables that would be used to obtain
  ///                the given JSON response data.
  public init(
    data: JSONObject,
    variables: GraphQLOperation.Variables? = nil
  ) throws {
    let accumulator = GraphQLSelectionSetMapper<Self>(
      handleMissingValues: .allowForOptionalFields
    )
    let executor = GraphQLExecutor { object, info in
      return object[info.responseKeyForField]
    }
    executor.shouldComputeCachePath = false

    self = try executor.execute(
      selectionSet: Self.self,
      on: data,
      variables: variables,
      accumulator: accumulator
    )
  }

}

The obvious difference being ours uses the Apollo TestMockSelectionSetMapper

@AnthonyMDev
Copy link
Contributor

What does your call site look like in your test? The from(_ mock:withVariables:) function is present in 1.1.1 and on.

@alexjameslittle
Copy link

Now that you mention it, it looks like it's some convenience extensions of ours that have confused things and directly initialised the Data instead of using from(_ mock:withVariables:).

This was our own implementation issue, sorry!

@AnthonyMDev
Copy link
Contributor

Ah gotcha, that makes sense! In order to do that with the mocks, you must have been accessing the _selectionSetMockData property. Underscore prefixed properties are meant for internal use only for this very reason. :) You can use them if you want to, but they allow you to make more bugs for yourself. If you try to avoid using our internal functions, you wouldn't be able to do this, and would be forced to use the correct function.

Glad you resolved this issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Generally incorrect behavior needs investigation planned-next Slated to be included in the next release
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants