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);
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With