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

Modify fragment location for GraphQl Tests #1092

Open
emaarco opened this issue Dec 2, 2024 · 3 comments
Open

Modify fragment location for GraphQl Tests #1092

emaarco opened this issue Dec 2, 2024 · 3 comments
Labels
status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged

Comments

@emaarco
Copy link

emaarco commented Dec 2, 2024

📋 Summary

I suggest modifying how file-based GraphQL fragments are handled in tests by allowing fragments to be sourced from a dedicated graphql-fragments folder rather than the current graphql-test folder.

🛑 Problem Statement

Currently, Spring GraphQL requires file-based fragments for tests to be placed in a graphql-test folder, which limits their use as a central resource.

In our platform, we have multiple backend services, each with GraphQL APIs that often return similar types like Task or User. Clients frequently request these entire objects, which leads to duplicated code across many queries and mutations. For example, every time we need a User object, we manually list all attributes, which is cumbersome and error-prone.

GraphQL fragments solve this problem by letting us define shared structures once, reducing repetition. However, since fragments aren't part of the GraphQL Schema Definition Language (SDL) and can't be accessed via introspection, clients must also define them separately, leading to duplication between backend and frontend code.

To address this, we want to create a central fragment registry in a backend service that all clients can use (f.ex. by synching them). This ensures consistency and keeps the backend as the single source of truth.

The current restriction to graphql-test is misleading, as it implies the fragments are test-only, rather than globally reusable.

🔍 Example

Consider the following example of a GraphQL fragment that defines a fragment for a User:

# In file user.fragments.graphqls
fragment UserDetails on UserDto {
    id
    name
    email
    phoneNumber
}

Below is a test that makes use of this UserDetails fragment. The user.fragment file referenced must be placed in a graphql-test folder:

graphQlTester.documentName("user")
        .fragmentName("user.fragment")
        .variable("userId", user.getId().toString())
        .execute()
        .path("user")
        .entity(UserDto.class)
        .isEqualTo(
            new UserDto(
                user.getId().toString(),
                user.getName(),
                user.getEmail(),
                user.getPhoneNumber()
            )
        );

💡 Proposed Solution

Allow file-based fragments to be placed in a graphql-fragments folder instead of graphql-test. This will better support the idea of a central fragment registry that can be used across both backend services and clients.

🚀 Benefits

  • Central Fragment Repository: Enables a central repository where fragments are consistently shared across all services and clients.
  • Improved Developer Experience: Separates fragments from test-specific content, reducing confusion.

🤝 Contribution

I am willing to contribute to this feature and have already designed a first implementation to check if this approach is possible. If you are considering the implementation, I am happy to further refine it based on your feedback.

💬 Feedback Request

I would greatly appreciate your feedback on this suggestion. Do you see any challenges or alternative approaches that could better support this use case? Please let me know your thoughts, and I'd be happy to discuss this further.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Dec 2, 2024
@bclozel
Copy link
Member

bclozel commented Dec 2, 2024

Hi @emaarco , thanks for this detailed report.

I think we already allow the GraphQlTester to resolve documents and fragments against several locations. You can configure a custom documentSource on the tester, using a ResourceDocumentSource that looks up files in several locations.

The current restriction to graphql-test is misleading, as it implies the fragments are test-only, rather than globally reusable.

I think that part was partially intentional. The feature request in #964 was focusing on testing support and I don't think we support fragments with our non-test client right now. I guess you tried using fragments with the GraphQlClient? How is this going?

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label Dec 2, 2024
@emaarco
Copy link
Author

emaarco commented Dec 2, 2024

hi @bclozel - thank you for your fast response! :)

I saw in DefaultGraphQlTester that it is fixed to the graphql-test folder due to the AbstractGraphQLTesterBuilder providing this. So I'm still a bit unsure how to create a lightweight custom tester myself (apart from implementing the whole interface myself, which will probably result in me copying the DefaultTester). Could you clarify where exactly I can configure a custom documentSource - or do I need to create a completely custom tester for this?

private static DocumentSource initDocumentSource() {
    return new ResourceDocumentSource(
            Collections.singletonList(new ClassPathResource("graphql-test/")),
            ResourceDocumentSource.FILE_EXTENSIONS);
}

To answer your question about my current usage (if I have understood it correctly):
I am not using fragments with the GraphQlClient directly, as my use case focuses on client-server communication rather than inter-service requests. I have an Angular frontend using Apollo Client for API generation and request handling, along with a GraphQL Yoga-powered gateway, and multiple backend services powered by Spring GraphQL. Currently, I store fragments in the graphql-test folder and synchronize them during specific actions like startup or build.

My use of fragments is focused solely on requests coming from the frontend to the backend.
The fragments work perfectly in this setup. Here is an (log-)example of a typical request, where the fragment is directly included in the query-string.

{
  "variables": {
    "userId": "example-user-id"
  },
  "document": "query getUser($userId: ID!) {
    __typename
    user(userId: $userId) {
      __typename
      ...UserFields
    }
  }
  fragment UserFields on UserDto {
    __typename
    id
    username
    email
    firstName
    lastName
    roles
    createdAt
    updatedAt
  }"
}

My goal is to provide the fragments in a more general-purpose folder to make them reusable beyond testing contexts. I feel that this could be beneficial not only for my project but potentially for others as well. Thus i decided to open this request to discuss this :)

image

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 2, 2024
@emaarco
Copy link
Author

emaarco commented Dec 19, 2024

@bclozel - I was able to create a custom GraphQl tester.

Do you have the feeling that there is some point in the documentation where you could add how to solve such a use case? I think that would have saved me a lot of time in solving the problem. Otherwise I think you could close the issue :)

With such a tester, I am now able to save fragments in a separate folder.
If anyone is facing a similar or the same problem - here is my current configuration for the tester:

@Configuration
class GraphQlTestConfiguration {

    @Bean
    @Primary
    fun customGraphQlTester(
        executionGraphQlTester: ExecutionGraphQlService,
    ): GraphQlTester {
        val customDocumentSource = documentSource()
        return ExecutionGraphQlServiceTester.builder(executionGraphQlTester)
            .documentSource(customDocumentSource)
            .encoder(Jackson2JsonEncoder())
            .decoder(Jackson2JsonDecoder())
            .build()
    }

    private fun documentSource(): DocumentSource {
        val resources = listOf(ClassPathResource("graphql-test/"), ClassPathResource("graphql-fragments/"))
        return ResourceDocumentSource(resources, ResourceDocumentSource.FILE_EXTENSIONS)
    }
}

The complete code can be found here
spring-gardium-leviosa

If anyone can think of a simpler solution, I would appreciate your feedback! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided status: waiting-for-triage An issue we've not yet triaged
Projects
None yet
Development

No branches or pull requests

3 participants