mock.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. Object.defineProperty(exports, "__esModule", { value: true });
  2. var graphql_1 = require("graphql");
  3. var uuid = require("uuid");
  4. var makeExecutableSchema_1 = require("./makeExecutableSchema");
  5. // This function wraps addMockFunctionsToSchema for more convenience
  6. function mockServer(schema, mocks, preserveResolvers) {
  7. if (preserveResolvers === void 0) { preserveResolvers = false; }
  8. var mySchema;
  9. if (!(schema instanceof graphql_1.GraphQLSchema)) {
  10. // TODO: provide useful error messages here if this fails
  11. mySchema = makeExecutableSchema_1.buildSchemaFromTypeDefinitions(schema);
  12. }
  13. else {
  14. mySchema = schema;
  15. }
  16. addMockFunctionsToSchema({ schema: mySchema, mocks: mocks, preserveResolvers: preserveResolvers });
  17. return { query: function (query, vars) { return graphql_1.graphql(mySchema, query, {}, {}, vars); } };
  18. }
  19. exports.mockServer = mockServer;
  20. var defaultMockMap = new Map();
  21. defaultMockMap.set('Int', function () { return Math.round(Math.random() * 200) - 100; });
  22. defaultMockMap.set('Float', function () { return Math.random() * 200 - 100; });
  23. defaultMockMap.set('String', function () { return 'Hello World'; });
  24. defaultMockMap.set('Boolean', function () { return Math.random() > 0.5; });
  25. defaultMockMap.set('ID', function () { return uuid.v4(); });
  26. // TODO allow providing a seed such that lengths of list could be deterministic
  27. // this could be done by using casual to get a random list length if the casual
  28. // object is global.
  29. function addMockFunctionsToSchema(_a) {
  30. var schema = _a.schema, _b = _a.mocks, mocks = _b === void 0 ? {} : _b, _c = _a.preserveResolvers, preserveResolvers = _c === void 0 ? false : _c;
  31. if (!schema) {
  32. throw new Error('Must provide schema to mock');
  33. }
  34. if (!(schema instanceof graphql_1.GraphQLSchema)) {
  35. throw new Error('Value at "schema" must be of type GraphQLSchema');
  36. }
  37. if (!isObject(mocks)) {
  38. throw new Error('mocks must be of type Object');
  39. }
  40. // use Map internally, because that API is nicer.
  41. var mockFunctionMap = new Map();
  42. Object.keys(mocks).forEach(function (typeName) {
  43. mockFunctionMap.set(typeName, mocks[typeName]);
  44. });
  45. mockFunctionMap.forEach(function (mockFunction, mockTypeName) {
  46. if (typeof mockFunction !== 'function') {
  47. throw new Error("mockFunctionMap[" + mockTypeName + "] must be a function");
  48. }
  49. });
  50. var mockType = function (type, typeName, fieldName) {
  51. // order of precendence for mocking:
  52. // 1. if the object passed in already has fieldName, just use that
  53. // --> if it's a function, that becomes your resolver
  54. // --> if it's a value, the mock resolver will return that
  55. // 2. if the nullableType is a list, recurse
  56. // 2. if there's a mock defined for this typeName, that will be used
  57. // 3. if there's no mock defined, use the default mocks for this type
  58. return function (root, args, context, info) {
  59. // nullability doesn't matter for the purpose of mocking.
  60. var fieldType = graphql_1.getNullableType(type);
  61. var namedFieldType = graphql_1.getNamedType(fieldType);
  62. if (root && typeof root[fieldName] !== 'undefined') {
  63. var result = void 0;
  64. // if we're here, the field is already defined
  65. if (typeof root[fieldName] === 'function') {
  66. result = root[fieldName](root, args, context, info);
  67. if (result instanceof MockList) {
  68. result = result.mock(root, args, context, info, fieldType, mockType);
  69. }
  70. }
  71. else {
  72. result = root[fieldName];
  73. }
  74. // Now we merge the result with the default mock for this type.
  75. // This allows overriding defaults while writing very little code.
  76. if (mockFunctionMap.has(namedFieldType.name)) {
  77. result = mergeMocks(mockFunctionMap
  78. .get(namedFieldType.name)
  79. .bind(null, root, args, context, info), result);
  80. }
  81. return result;
  82. }
  83. if (fieldType instanceof graphql_1.GraphQLList ||
  84. fieldType instanceof graphql_1.GraphQLNonNull) {
  85. return [
  86. mockType(fieldType.ofType)(root, args, context, info),
  87. mockType(fieldType.ofType)(root, args, context, info),
  88. ];
  89. }
  90. if (mockFunctionMap.has(fieldType.name) &&
  91. !(fieldType instanceof graphql_1.GraphQLUnionType ||
  92. fieldType instanceof graphql_1.GraphQLInterfaceType)) {
  93. // the object passed doesn't have this field, so we apply the default mock
  94. return mockFunctionMap.get(fieldType.name)(root, args, context, info);
  95. }
  96. if (fieldType instanceof graphql_1.GraphQLObjectType) {
  97. // objects don't return actual data, we only need to mock scalars!
  98. return {};
  99. }
  100. // if a mock function is provided for unionType or interfaceType, execute it to resolve the concrete type
  101. // otherwise randomly pick a type from all implementation types
  102. if (fieldType instanceof graphql_1.GraphQLUnionType ||
  103. fieldType instanceof graphql_1.GraphQLInterfaceType) {
  104. var implementationType = void 0;
  105. if (mockFunctionMap.has(fieldType.name)) {
  106. var interfaceMockObj = mockFunctionMap.get(fieldType.name)(root, args, context, info);
  107. if (!interfaceMockObj || !interfaceMockObj.__typename) {
  108. return Error("Please return a __typename in \"" + fieldType.name + "\"");
  109. }
  110. implementationType = schema.getType(interfaceMockObj.__typename);
  111. }
  112. else {
  113. var possibleTypes = schema.getPossibleTypes(fieldType);
  114. implementationType = getRandomElement(possibleTypes);
  115. }
  116. return Object.assign({ __typename: implementationType }, mockType(implementationType)(root, args, context, info));
  117. }
  118. if (fieldType instanceof graphql_1.GraphQLEnumType) {
  119. return getRandomElement(fieldType.getValues()).value;
  120. }
  121. if (defaultMockMap.has(fieldType.name)) {
  122. return defaultMockMap.get(fieldType.name)(root, args, context, info);
  123. }
  124. // if we get to here, we don't have a value, and we don't have a mock for this type,
  125. // we could return undefined, but that would be hard to debug, so we throw instead.
  126. // however, we returning it instead of throwing it, so preserveResolvers can handle the failures.
  127. return Error("No mock defined for type \"" + fieldType.name + "\"");
  128. };
  129. };
  130. makeExecutableSchema_1.forEachField(schema, function (field, typeName, fieldName) {
  131. assignResolveType(field.type, preserveResolvers);
  132. var mockResolver;
  133. // we have to handle the root mutation and root query types differently,
  134. // because no resolver is called at the root.
  135. /* istanbul ignore next: Must provide schema DefinitionNode with query type or a type named Query. */
  136. var isOnQueryType = schema.getQueryType() && schema.getQueryType().name === typeName;
  137. var isOnMutationType = schema.getMutationType() && schema.getMutationType().name === typeName;
  138. if (isOnQueryType || isOnMutationType) {
  139. if (mockFunctionMap.has(typeName)) {
  140. var rootMock_1 = mockFunctionMap.get(typeName);
  141. // XXX: BUG in here, need to provide proper signature for rootMock.
  142. if (typeof rootMock_1(undefined, {}, {}, {})[fieldName] === 'function') {
  143. mockResolver = function (root, args, context, info) {
  144. var updatedRoot = root || {}; // TODO: should we clone instead?
  145. updatedRoot[fieldName] = rootMock_1(root, args, context, info)[fieldName];
  146. // XXX this is a bit of a hack to still use mockType, which
  147. // lets you mock lists etc. as well
  148. // otherwise we could just set field.resolve to rootMock()[fieldName]
  149. // it's like pretending there was a resolve function that ran before
  150. // the root resolve function.
  151. return mockType(field.type, typeName, fieldName)(updatedRoot, args, context, info);
  152. };
  153. }
  154. }
  155. }
  156. if (!mockResolver) {
  157. mockResolver = mockType(field.type, typeName, fieldName);
  158. }
  159. if (!preserveResolvers || !field.resolve) {
  160. field.resolve = mockResolver;
  161. }
  162. else {
  163. var oldResolver_1 = field.resolve;
  164. field.resolve = function (rootObject, args, context, info) {
  165. return Promise.all([
  166. mockResolver(rootObject, args, context, info),
  167. oldResolver_1(rootObject, args, context, info),
  168. ]).then(function (values) {
  169. var mockedValue = values[0], resolvedValue = values[1];
  170. // In case we couldn't mock
  171. if (mockedValue instanceof Error) {
  172. // only if value was not resolved, populate the error.
  173. if (undefined === resolvedValue) {
  174. throw mockedValue;
  175. }
  176. return resolvedValue;
  177. }
  178. if (resolvedValue instanceof Date && mockedValue instanceof Date) {
  179. return undefined !== resolvedValue ? resolvedValue : mockedValue;
  180. }
  181. if (isObject(mockedValue) && isObject(resolvedValue)) {
  182. // Object.assign() won't do here, as we need to all properties, including
  183. // the non-enumerable ones and defined using Object.defineProperty
  184. var emptyObject = Object.create(Object.getPrototypeOf(resolvedValue));
  185. return copyOwnProps(emptyObject, resolvedValue, mockedValue);
  186. }
  187. return undefined !== resolvedValue ? resolvedValue : mockedValue;
  188. });
  189. };
  190. }
  191. });
  192. }
  193. exports.addMockFunctionsToSchema = addMockFunctionsToSchema;
  194. function isObject(thing) {
  195. return thing === Object(thing) && !Array.isArray(thing);
  196. }
  197. // returns a random element from that ary
  198. function getRandomElement(ary) {
  199. var sample = Math.floor(Math.random() * ary.length);
  200. return ary[sample];
  201. }
  202. function mergeObjects(a, b) {
  203. return Object.assign(a, b);
  204. }
  205. function copyOwnPropsIfNotPresent(target, source) {
  206. Object.getOwnPropertyNames(source).forEach(function (prop) {
  207. if (!Object.getOwnPropertyDescriptor(target, prop)) {
  208. Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
  209. }
  210. });
  211. }
  212. function copyOwnProps(target) {
  213. var sources = [];
  214. for (var _i = 1; _i < arguments.length; _i++) {
  215. sources[_i - 1] = arguments[_i];
  216. }
  217. sources.forEach(function (source) {
  218. var chain = source;
  219. while (chain) {
  220. copyOwnPropsIfNotPresent(target, chain);
  221. chain = Object.getPrototypeOf(chain);
  222. }
  223. });
  224. return target;
  225. }
  226. // takes either an object or a (possibly nested) array
  227. // and completes the customMock object with any fields
  228. // defined on genericMock
  229. // only merges objects or arrays. Scalars are returned as is
  230. function mergeMocks(genericMockFunction, customMock) {
  231. if (Array.isArray(customMock)) {
  232. return customMock.map(function (el) { return mergeMocks(genericMockFunction, el); });
  233. }
  234. if (isObject(customMock)) {
  235. return mergeObjects(genericMockFunction(), customMock);
  236. }
  237. return customMock;
  238. }
  239. function getResolveType(namedFieldType) {
  240. if (namedFieldType instanceof graphql_1.GraphQLInterfaceType ||
  241. namedFieldType instanceof graphql_1.GraphQLUnionType) {
  242. return namedFieldType.resolveType;
  243. }
  244. else {
  245. return undefined;
  246. }
  247. }
  248. function assignResolveType(type, preserveResolvers) {
  249. var fieldType = graphql_1.getNullableType(type);
  250. var namedFieldType = graphql_1.getNamedType(fieldType);
  251. var oldResolveType = getResolveType(namedFieldType);
  252. if (preserveResolvers && oldResolveType && oldResolveType.length) {
  253. return;
  254. }
  255. if (namedFieldType instanceof graphql_1.GraphQLUnionType ||
  256. namedFieldType instanceof graphql_1.GraphQLInterfaceType) {
  257. // the default `resolveType` always returns null. We add a fallback
  258. // resolution that works with how unions and interface are mocked
  259. namedFieldType.resolveType = function (data, context, info) {
  260. return info.schema.getType(data.__typename);
  261. };
  262. }
  263. }
  264. var MockList = /** @class */ (function () {
  265. // wrappedFunction can return another MockList or a value
  266. function MockList(len, wrappedFunction) {
  267. this.len = len;
  268. if (typeof wrappedFunction !== 'undefined') {
  269. if (typeof wrappedFunction !== 'function') {
  270. throw new Error('Second argument to MockList must be a function or undefined');
  271. }
  272. this.wrappedFunction = wrappedFunction;
  273. }
  274. }
  275. MockList.prototype.mock = function (root, args, context, info, fieldType, mockTypeFunc) {
  276. var arr;
  277. if (Array.isArray(this.len)) {
  278. arr = new Array(this.randint(this.len[0], this.len[1]));
  279. }
  280. else {
  281. arr = new Array(this.len);
  282. }
  283. for (var i = 0; i < arr.length; i++) {
  284. if (typeof this.wrappedFunction === 'function') {
  285. var res = this.wrappedFunction(root, args, context, info);
  286. if (res instanceof MockList) {
  287. var nullableType = graphql_1.getNullableType(fieldType.ofType);
  288. arr[i] = res.mock(root, args, context, info, nullableType, mockTypeFunc);
  289. }
  290. else {
  291. arr[i] = res;
  292. }
  293. }
  294. else {
  295. arr[i] = mockTypeFunc(fieldType.ofType)(root, args, context, info);
  296. }
  297. }
  298. return arr;
  299. };
  300. MockList.prototype.randint = function (low, high) {
  301. return Math.floor(Math.random() * (high - low + 1) + low);
  302. };
  303. return MockList;
  304. }());
  305. exports.MockList = MockList;
  306. //# sourceMappingURL=mock.js.map