GraphQL essentially allows users to define queries from a front-end to gather concrete data. So as it looks it is completely different than a standard REST endpoint. GraphQL differs queries which only gather data from those which also mutates them. In this article, I want to concentrate on queries which only gather some data and how to test them via FsCheck and F#.
some time ago I had a pleasure to join a project which uses a GraphQL library. GraphQL essentially allows users to define queries from a front-end to gather concrete data. So as it looks it is completely different than a standard REST endpoint. GraphQL differs queries which only gather data from those which also mutates them. In this article, I want to concentrate on queries which only gather some data and how to test them.
I don’t want to go deep into GraphQL details and how to create some GraphQL query. I assume that we have a query in which we could ask about car details. Model for beforehand said Car looks like this:
And finally a query:
Thanks to that we could ask for a data a GraphQL endpoint like this:
Of course, as I said before we don’t need to specify all fields in an expected result. The query could be highly modifiable, so I thought it would be great idea to write some FsCheck tests, which would scan a GraphQL graph with a query and based on them generates sample queries. Thanks to which I could check a couple of properties. One of them would be to check if there are not any errors sections in GraphQL response, the second one would be to check if all responses end with a 200 HTTP Status Code. Thanks to the above I could be one hundred percent sure that all queries would work fine (maybe not from a business perspective :)), especially that those tests would be included in a CI pipeline as a part of integration tests.
To use a FsCheck library I have to write a generator for an input data. As an input data, I assume a serialized query. The project is written in a C#, so I used this language to write a generator.
Starting from a generic generator signature I assume that every query has to implement a method to collect data needed to create queries with arguments on their own. Also, every generator would know which class represents those arguments. So the signature of a generator looks as follows.
As we see TGraphQuery is responsible for a query type which we want to be our subject under test while TArguments stands for an accepted arguments by query.
Going to an implementation of a generator I would present the whole code and then describe it line by line.
We start by building a container for all dependencies. Next, we gather query of concrete IGraphType type (our generic type) from all registered RootQueryFields, we have to remember that all queries are registered as RootQueryFields so if we would try to gather concrete IGraphType from an AutoFac container we would get an exception. Then we get information about the graph and its instance from a container cause we need an information about defined GraphQL fields for that query which are defined in a constructor. Also, we need to retain that those types could be also a generic type like ListGraphType<OurCustomGraphType> so we want to gather a generic argument. We could do that thanks to the below method:
As we can see I don’t have here a stop guard cause I suppose (maybe incorrectly) that people don’t create infinite generic types.
Next phase is a check if query accepts some arguments, if yes we want to create a query with them if nope we only need fields for which we could ask. We are going to the implementation of a generator with arguments.
As we could see we get all arguments, then query name and all fields for which we could ask. Implementation of a GetFieldRepresentation looks as follows:
It based on gathering field name if the type of this field is a simple GraphlQL type (for example IntGraphType etc. ) if the type is more complex we want to gather all fields of this complex field as long as we reach a max deep of 3.
Going back to the previous method. Based on gathered fields, which are in a form of string list, we create a generator rest on them. Then, we get all arguments for a query based on a function which is passed as an argument. Next, at the end of this function we create our final generator which depends on fields and arguments, we also keep in mind that our query has to contain some fields, so we have to filter out a situation where fields list is empty.
Finally, we build test object thanks to the BuildQuery function, which looks like this:
As it seems it isn’t a rocket science, only a combination of a graphQL query.
Implementation of a method responsible for the creation of a query without arguments looks very similar to this with arguments:
The question is how concrete generator for cars would look like?
As we could see generator for a concrete query is very simple and readable, tests look like this:
To sum up, we could create very easily a FsCheck generator so that a GraphQL query would be tested almost in 100% in case of some lacks in implementation. It helps us in delivering a new version of our application which couldn’t be spotted while writing some single unit tests or manual tests.