apollographql / apollo-ios

📱  A strongly-typed, caching GraphQL client for iOS, written in Swift.
https://www.apollographql.com/docs/ios/
MIT License
3.87k stars 717 forks source link

Crash in test code working with nullable enums #2813

Open samus opened 1 year ago

samus commented 1 year ago

Summary

I'm working on migrating a code base to use 1.0 and encountering a strange crash while running unit tests. The schema being consumed has an enum that is nullable. In prior versions this was working fine. However now my tests crash with a sig abort when trying to access this one field. The code is failing in the InputDict struct inside the getter for the subscript operator. It is failing to cast a value of type ApolloAPI.GraphQLNullable<ApolloAPI.GraphQLEnum<MyGraphqlSchema.PetPolicyFilter>> to what appears to be the same type.

I'm only experiencing this crash while running unit tests. When I run the app in the simulator similar code works with no issue. It is also only happening inside an extension function

Version

1.0.6

Steps to reproduce the behavior

        let mock = Mock<PetPolicy>(
                                   comment: "comment",
                                   id: .case(.catsAllowed) // optional enum
        )

        let petPolicy = ListingDetailFragment.PetPolicy.from(mock)
        // This works fine
        let id = petPolicy.id
        print(id)

        // This will fail when self.id is accessed in the extension method.
        let listingPetPolicy = petPolicy.toListingPetPolicy()

The extension method has some code to convert from the back end type to an app specific type.

extension ListingDetailFragment.PetPolicy {
    public func toListingPetPolicy() -> ListingPetPolicy {
        print(self.id) // This fails.
        ...
    }
}

This is using Xcode 14.2. I have tried switching the apollo dependency to use the main and release/1.1 branches but the problem is still the same.

Logs

Could not cast value of type 'ApolloAPI.GraphQLNullable<ApolloAPI.GraphQLEnum<MyGraphqlSchema.PetPolicyFilter>>' (0x14e030398) to
'ApolloAPI.GraphQLNullable<ApolloAPI.GraphQLEnum<MyGraphqlSchema.PetPolicyFilter>>' (0x13e81ca48).

Anything else?

No response

calvincestari commented 1 year ago

Hi @samus, I think you're running into the same issue we've had reported in https://github.com/apollographql/apollo-ios/issues/2686.

Is your project/dependency configuration the same as what I've described in https://github.com/apollographql/apollo-ios/issues/2686#issuecomment-1416297673? Using an Xcode-based project with Apollo as an SPM dependency with the test mocks being generated into the SPM schema module?

samus commented 1 year ago

Yes it is very similar. I have an app target and a business logic framework that are defined in Xcode with the framework and framework tests referencing the schema SPM defined framework. Now that I scrolled further back in the log I also see the class implemented in two modules warning. You mentioned the only way you found around that was to make the schema framework dynamic. Is that recommended?

calvincestari commented 1 year ago

I suspect you have the test mocks being generated into the schema module too? If so you could try generate them to an absolute path outside of the generated SPM module. They will need to be added manually to you test target but that may remove part of the dependency problem; I haven't tried this yet.

You mentioned the only way you found around that was to make the schema framework dynamic. Is that recommended?

It can affect app size and startup performance mainly; there are tradeoffs to this approach and it's something that should be considered per project. This article does a good job of deep diving into the differences. I wouldn't just jump to the conclusion that it's bad though. Best if you can measure the tradeoffs and make a decision based on data.

samus commented 1 year ago

Following up on this after a lot of project restructuring. I'm able to use the mocks without crashing now. My project structure is an Xcode project with an App target. The app target depends on a Swift Package containing the business logic. That package defines a business logic static lib and a test target. The business logic package depends on an app graphql schema package that includes generated test mocks. The tests in the business logic package are able to use the test mocks without any trouble using this structure. I did have to extract my protocols into a separate package that is a dynamic library in order to eliminate the last of the duplicate symbols. The final structure looks something like:

App |- DomainPackage (models and protocols) |- BusinessLogicPackage |- BusinessLogic - Depends on Apollo and generated GraphQL Schema Package |- BusinessLogicTests - Depends on BusinessLogic target & GraphQL Schema Mocks

calvincestari commented 1 year ago

Thank you for circling back and helping with that update @samus, much appreciated.