teamwalnut / graphql-ppx

GraphQL language primitives for ReScript/ReasonML written in ReasonML
https://graphql-ppx.com
MIT License
258 stars 53 forks source link

Feature Request/Idea: Factories #216

Closed kgoggin closed 3 years ago

kgoggin commented 3 years ago

it'd be really handy if a generated GraphQL module contained a factory/make function for easily creating an instance of the query/fragment data for use in tests/storybook stories. For instance:

[%graphql {|
fragment Avatar on Person {
  id
  firstName
  lastName
}
|}

let testAvatarData = Avatar.make(~id="foo", ~firstName="Fred", ~lastName="Fredrickson");

Considerations

  1. It'd add to the overall size of the code that gets generated while it's also likely this would never get used in a prod setting.
  2. To allow for composing fragments, the make function would need to contain an argument for each field present in the final composed type, generate the resulting JSON, and then call parse on it to get the fragments broken out correctly. BUT, that also limits the ability to compose these generated fixtures. I'm not sure if it'd be better to have an argument for the composed fragment's type itself and be okay with the fact that the overlapping fields aren't consistent?

I'd be really curious if anyone else has any way they're doing this currently. For the time being I'm manually writing a Js.t that matches the data structure I expect and coercing it with Obj.magic, but I'd really love a safer option that'd handle when I add a new field to the fragment/query automatically. I realized I could just define a record of the actual type which is at least type safe :). Still seems nice to be able to handle the fragment composition though.

jfrolich commented 3 years ago

We have makeVariables to construct the variables (or directly use the variables record), and you can pass the whole module of the query as a first class module.

You can use:

TestData.make((module Avatar), {id: "foo", firstName: "Fred", lastName: "Fredrickson"})

or implemented as an extension

Avatar.makeTestData({id: "foo", firstName: "Fred", lastName: "Fredrickson"})
jfrolich commented 3 years ago

Oh sorry, you are not talking about variables here. To get valid test data you can basically just construct the Avatar.t record yourself, it is typesafe so that means that the test data always has the correct shape! In this case it's just:

let avatar = {Avatar.id: "foo", firstName: "Fred", lastName: "Fredrickson"}

You can possibly wrap it in a factory function yourself to reduce boilerplate when you create a lot of users.

jfrolich commented 3 years ago

You can either construct Avatar.t and construct the reason data types. Or you can construct Avatar.Raw.t to construct the JSON compatible data types and then convert with Avatar.parse. If you want to go from raw JSON to Avatar.Raw.t you can use Avatar.unsafe_fromJson, but that is unsafe and you need to be sure that this is a valid JSON result coming from this query.

jfrolich commented 3 years ago

I don't get your point about fragment composition. You can just construct the whole data structure, but you might need some type hints here and there because of the nominal typed records.

kgoggin commented 3 years ago

Yeah, sorry that wasn't as clear. I was referring to the idea that a factory could contain only the unique fields, not worrying about fields that are shared between fragments. And, in thinking about it some more, what I had in mind probably isn't that great anyway when it comes to dealing with nested objects. I think you're probably right that this is better handled by creating project-specific factories where needed than at the PPX level. Thanks for your response!