Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Spring boot + GraphQL api (query + mutation) with headers and variables

I was facing a lot of problems while searching for answer: "How to test graphQL api nowdays". I m talking about smth more serious than simple findById schema.graphqls. And after 2 days of searching i didnt find anything modern (a lot of tutorials using resolvers, which are from last centure) and many other issues. I hope this information will help someone who is new to graphQl and will save your time in digging this. Here is my schema.graphqls (you can find how to leave comments for /graphiql + how to set default values + scalars):

   scalar DateTime

type Query {
    
    """
    *leave your comments here.*
    """
    getCommunicationInteraction(id: String, number: String, callId: String): CommunicationInteraction
}

type Mutation {
    createCommunicationInteraction(communicationInteraction: CommunicationInteractionInput): CreateCommunicationInteractionResponse
}

type CommunicationInteraction {
    id: String
    number: String
    channel: String
    status: String
    interactionClass: String
    typeValue: String
    createDate: DateTime
    emailSenderAddress: String
    callId: String
    ownerLogin: String
    userNick: String
    parentServiceRequestId: String
    contactId: String
    contractId: String
    productId: String
    customerIdentification: CustomerIdentification
}

input CommunicationInteractionInput {
    id: String
    number: String
    channel: String
    status: String
    interactionClass: String
    typeValue: String
    createDate: DateTime
    emailSenderAddress: String
    callId: String
    ownerLogin: String
    userNick: String
    parentServiceRequestId: String
    contactId: String
    contractId: String
    productId: String
    customerIdentification: CustomerIdentificationInput
}

type CustomerIdentification {
    msisdn: String
    accountNum: String
    agreementNum: String
    partyId: String
    terminalDeviceId: String
    clientBase: String
}

input CustomerIdentificationInput {
msisdn: String
accountNum: String
agreementNum: String
partyId: String
terminalDeviceId: String
clientBase: String
}

type CreateCommunicationInteractionResponse{
    id: String
    number: String
}

I m not going to describe my service and repository, there is nothing special there, but here what my controller looks like (you can see how i get headers here):

@Controller
@Validated
@AllArgsRequired
public class CommunicationInteractionGraphQLController {
    private final String HEADER = "x-channel-id";
    private final HttpServletRequest request;

    @QueryMapping
    CommunicationInteraction getCommunicationInteraction(
            @Argument String id,
            @Argument String number,
            @Argument String callId
    ) {
        String header = request.getHeader(HEADER);
        if(!hasText(header)){
            throw new NoContentException("No header \"x-channel-id\" were passed");
        }

        return getCommunicationInteractionService.getCommunicationInteraction(id, number, callId).orElseThrow(
                () -> new NoContentException("CommunicationInteraction not found with these params"));
    }

    @MutationMapping
    public CreateCommunicationInteractionResponse createCommunicationInteraction(@Argument CommunicationInteraction communicationInteraction){
        String header = request.getHeader(HEADER);
        if(!hasText(header)){
            throw new NoContentException("No header \"x-channel-id\" were passed");
        }
        return createCommunicationInteractionService.createCommunicationInteraction(communicationInteraction, header);
    }
}

Here is what i had in the beginning. This is my pom.xml graphql part

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
      <version>3.1.0</version>
    </dependency>
<dependency>
      <groupId>org.springframework.graphql</groupId>
      <artifactId>spring-graphql-test</artifactId>
      <scope>test</scope>
    </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-graphql</artifactId>
    </dependency>
  <dependency>
      <groupId>com.graphql-java</groupId>
      <artifactId>graphql-java-extended-scalars</artifactId>
      <version>${graphql-java-extended-scalars.version}</version>
    </dependency>

and i mentioned scalars before, here is config:

@Configuration
public class ScalarConfiguration {
    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer() {
        return wiringBuilder -> wiringBuilder.scalar(ExtendedScalars.DateTime);
    }
}
like image 541
Nik Korn Avatar asked Jun 08 '26 01:06

Nik Korn


1 Answers

And here what i made after few days of struggling. Hope you can find it useful and I will be really grateful if you add something that could help me in future(i m using H2 for testing purposes):

@SpringBootTest(classes = CommunicationInteractionApp.class)
@AutoConfigureGraphQlTester
@ActiveProfiles("h2db")
class CommunicationInteractionGraphQLControllerTest {
    private final String HEADER = "header";

    @Autowired
    WebApplicationContext context;

    @Captor
    private ArgumentCaptor<CommunicationInteraction> captor;

    @Autowired
    GraphQlTester graphQlTester;

    @Autowired
    ObjectMapper mapper;

    @MockBean
    CreateCommunicationInteractionService createCommunicationInteractionService;

    @Test
    void getCommunicationInteraction() {
        String query = "{ getCommunicationInteraction(id: \"1-EUFRPB3\") { number status createDate callId } }";

        WebTestClient.Builder webTestClientBuilder = MockMvcWebTestClient.bindToApplicationContext(context)
                .configureClient()
                .baseUrl("/graphql");

        HttpGraphQlTester tester = HttpGraphQlTester
                .builder(webTestClientBuilder)
                .header("x-channel-id", HEADER)
                .build();

        CommunicationInteraction communicationInteraction = tester.document(query)
                .execute()
                .path("getCommunicationInteraction")
                .entity(CommunicationInteraction.class)
                .get();
        Assertions.assertNotNull(communicationInteraction);
        assertEquals("1-32315424735", communicationInteraction.getNumber());
        assertEquals("790311234567", communicationInteraction.getCallId());
        assertEquals("2023-06-06T12:00Z", communicationInteraction.getCreateDate().toString());
    }

    @Test
    void createCommunicationInteraction() {
        String query = """
                        mutation createCommunicationInteraction($communicationInteraction: CommunicationInteractionInput) {
                          createCommunicationInteraction(communicationInteraction: $communicationInteraction) {
                            id
                          }
                        }
                """;
        var request = Map.of("id", "id", "number", "number");

        CreateCommunicationInteractionResponse siebelResponse = new CreateCommunicationInteractionResponse("id", "number");

        Mockito.when(createCommunicationInteractionService.createCommunicationInteraction(captor.capture(), eq(HEADER))).thenReturn(siebelResponse);

        WebTestClient.Builder webTestClientBuilder = MockMvcWebTestClient.bindToApplicationContext(context)
                .configureClient()
                .baseUrl("/graphql");

        HttpGraphQlTester tester = HttpGraphQlTester
                .builder(webTestClientBuilder)
                .header("x-channel-id", HEADER)
                .build();

        CreateCommunicationInteractionResponse response = tester.document(query)
                .variable("communicationInteraction", request)
                .execute()
                .path("createCommunicationInteraction")
                .entity(CreateCommunicationInteractionResponse.class)
                .get();
        assertNotNull(response);

        CommunicationInteraction value = captor.getValue();

        assertEquals("id", value.getId());
        assertEquals("number", value.getNumber());
        assertEquals("header", HEADER);
    }

    @Test
    @DisplayName("No header error")
    void failWithNoHeader() {
        String query = "{ getCommunicationInteraction(id: \"1-EUFRPB3\") { number status createDate callId } }";

        graphQlTester.document(query)
                .execute()
                .errors()
                .satisfy(
                        responseErrors -> {
                            assertThat(responseErrors).hasSize(1);
                            assertThat(responseErrors.get(0).getErrorType().toString()).isEqualTo("NOT_FOUND");
                        });
    }
}

I faced some problems with mutations, cause incoming variable was POJO (or DTO), but in tests graphQltester understand only Map of params. And due to the Mock, i had to use @Captor. (passing variables didnt work for me).

I wasnt able to find correct way to add header but to create HttpGraphQlTester. And that is why IDEA told me to add webFlux dependency. Plus, according to documentationthe way of creating HttpGraphQlTester is slightly differs from mine (cause the way in documentation didn`t work for me).

To create correct mutation, i launched app locally and using /graphiql + F12, checked correct query.

How to work with errors, I found here and these videos too are very helpful.

like image 138
Nik Korn Avatar answered Jun 10 '26 20:06

Nik Korn