graphql-faker icon indicating copy to clipboard operation
graphql-faker copied to clipboard

Values from @example should be used as exact values

Open IvanGoncharov opened this issue 7 years ago • 11 comments

Reported by @sepehr500 in #25

Currently, if want to fake a string, I do the following

type CourtCase {
  jurisdiction: String @examples(values: ["DFT", "FUR"])
}

This makes the think that values expects it's value to be an array of whatever the property type is. So it would follow, that if the type was the following,

type CourtCase {
  jurisdiction: [String] @examples(values: [["DFT", "FUR"],["DFS","DNA"])
}

Since the property type is now an array of strings, it would make sense to pass in an array of strings into the array to match the return type. So if we take this same logic an apply it to Objects, then we would expect a property that has a type that is an array of objects, to look like the following,

type Query {
  allCourtCases: [CourtCase!]!
     @examples(values: [[{state: "WASH", capital: "SEA"},{state: "VA", capital: "RMD"}], [{state:"VA"}]])
}

This way, you have a consistent rule that values always expects an array of whatever type the return type of the property is.

IvanGoncharov avatar Sep 14 '17 17:09 IvanGoncharov

@sepehr500 Agree, it's not very logic behavior. Idea behind it was that @examples mimics @fake behavior and you need some mechanism to allow @fake on arrays, e.g.:

type Query {
  names: [String] @fake(type: "first_name")
}

Not sure how to solve this. What do you think about @exampleItems and @fakeItems? Would love to hear alternative ideas?

In any case, changing current behavior is breaking change and require 2.0.0 version. Don't want to block #25 so I split it out into the separate issue.

IvanGoncharov avatar Sep 14 '17 17:09 IvanGoncharov

what would you gain by doing @exampleItems and @fakeItems? I think it's better to just keep the logic that of "value takes an array of items that match the return type".

sepehr500 avatar Sep 14 '17 20:09 sepehr500

@sepehr500 Problem is that there are two types of scenarios I want to mock. Field that returns:

  1. exact array.
  2. random array consisting from provided elements.

For example

type Query {
  letters: [String] @examples(values: ["A", "B", "C"])
}

So there two scenarious:

  1. you always get ["A", "B", "C"]
  2. some combination of examples like: ["B", "C", A"], ["B", "B", "A"], etc.

I want to support both scenarios. Another variant to achieve both would be:

  1. @examples(values = [["A", "B", "C"]])
  2. @examples(items = ["A", "B", "C"])

IvanGoncharov avatar Sep 15 '17 10:09 IvanGoncharov

Might be better to have a flag that randomizes the order of what is returned. Personally, when I am doing local development, I usually don't prefer order being randomized every time. The page should not look totally different every time, just like when using the webpage normally. It also causes problems when GraphQL is ordering results(for example, order by date). So maybe a better solution is,

type Query {
  letters: [String] @examples(values: [["A", "B", "C"]], options: {randomize: true})
}

sepehr500 avatar Sep 15 '17 14:09 sepehr500

Might be better to have a flag that randomizes the order of what is returned. Personally, when I am doing local development, I usually don't prefer order being randomized every time. The page should not look totally different every time, just like when using the webpage normally.

@sepehr500 I think it should be handled on the global level. For next major version, I want to add /graphql/seed/{seed_number} endpoint to always have exactly the same result for an entire query including data generated by @fake. Or allow specifying seed inside query itself e.g.:

query @seed(number: 1234) {
  letters
}

So maybe a better solution is

I'm confused, I thought you advocated on specifying an array of return values inside values so it should be @examples(values: [["A", "B", "C"]] (note additional square brackets). Or I'm missing something?

IvanGoncharov avatar Sep 15 '17 17:09 IvanGoncharov

My mistake! It was a typo and I fixed it. I like the ease of being able to specify random order on a global level, but like the granularity of being able to specify random order on a field level as well. Why not both?

sepehr500 avatar Sep 15 '17 17:09 sepehr500

@sepehr500 Thank you for suggestions 👍 Right now working on a new GraphQL project, so will put this issue on hold. But after releasing it I will try to iterate on this and other issues and release version 2.0.0.

IvanGoncharov avatar Sep 18 '17 21:09 IvanGoncharov

Hi, I was just wondering if this was something you still plan to add as part of the version 2.0.0 release? It would be great to specify an exact array to return, for example:

type Query {
  letters: [String] @examples(values: [["A", "B", "C"]])
}

Then running the query:

{
  letters
}

always returns the array ["A", "B", "C"] in that order. Thanks!

nmoore93 avatar Sep 26 '19 10:09 nmoore93

Over a year later since the last ping, and 2.0.0 is currently in RC phase. Wondering if there's any chance that this will be implemented? Finding myself needing it, and sorely disappointed that it's not there.

Specifically the use case I'm trying to solve is something like this:

interface IPlugin {
  name: String!
}

type Pluggable {
    plugins: [IPlugin!]! @examples(values: [[PluginA, PluginB], [PluginC, PluginD]])
}

type PluginA implements IPlugin {
  name: String! @fake(type: productName)
}

type PluginB implements IPlugin {
  name: String! @fake(type: productName)
}

type PluginC implements IPlugin {
  name: String! @fake(type: productName)
}

type PluginD implements IPlugin {
  name: String! @fake(type: productName)
}

type Query {
  pluggable: Pluggable
}

Here's a query which should show the sets.

{
  pluggable {
      plugins {
        __typename
        ... on PluginA {
          name
        }
        ... on PluginB {
          name
        }
        ... on PluginC {
          name
        }
        ... on PluginD {
          name
        }        
      }
  }
}

With the expected outcome to always return a collection with PluginA, and PluginB, or a collection with PluginC and PluginD.

ocgully avatar Jan 14 '21 00:01 ocgully

I did some work, but it's not PR ready. I was able to resolve the arrays to the schema types and get a list of the types I should create fakes of to return the array. But wasn't able to get the fakeTypeResolver to work from the fakeFieldResolver (see the function getExampleArrayValueCB). I'm attaching the patch of the work I did for review and hoping that someone else can help finish the work.

diff --git a/src/fake_schema.ts b/src/fake_schema.ts
index 1bc8cd0..de88a8a 100644
--- a/src/fake_schema.ts
+++ b/src/fake_schema.ts
@@ -102,6 +102,14 @@ export const fakeFieldResolver: GraphQLFieldResolver<unknown, unknown> = async (
     }
 
     if (isListType(type)) {
+      if (exampleValuesTypesAreArrays(fieldDef) ||
+        exampleValuesTypesAreArrays(type)) {
+
+        return getExampleArrayValueCB(fieldDef) ||
+          getExampleArrayValueCB(type);
+      }
+
+      // If not, then listen to the list length, and randomly generate
       return Array(getListLength(fieldDef))
         .fill(null)
         .map(() => fakeValueOfType(type.ofType));
@@ -143,6 +151,30 @@ export const fakeFieldResolver: GraphQLFieldResolver<unknown, unknown> = async (
     return args && (() => getRandomItem(args.values));
   }
 
+  function exampleValuesTypesAreArrays(object) {
+    const examplesDirective = schema.getDirective('examples');
+    const args = getDirectiveArgs(examplesDirective, object) as ExamplesArgs;
+    return args.values.find(e => Array.isArray(e));
+  }
+
+  function getExampleArrayValueCB(object) {
+    const examplesDirective = schema.getDirective('examples');
+    const args = getDirectiveArgs(examplesDirective, object) as ExamplesArgs;
+
+    var entry = getRandomItem(args.values);
+
+    if (Array.isArray(entry)){
+      var arr = Array(entry.length)
+        .fill(null)
+        .map((e) => fakeTypeResolver(schema.getType(e), context, info,  abstractType))
+
+      return arr;
+    }
+
+    return null;
+  }
+
   function getListLength(object): ListLengthArgs {
     const listLength = schema.getDirective('listLength');
     const args = getDirectiveArgs(listLength, object) as ListLengthArgs;

ocgully avatar Jan 15 '21 02:01 ocgully

My proposal https://github.com/graphql-kit/graphql-faker/pull/199

tomasjanicek avatar Sep 14 '23 08:09 tomasjanicek