diff --git a/README.md b/README.md index 53e5a02..62aa894 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@

GraphQL Ergonomock

- Developer-friendly automock for GraphQL + 🔮 Developer-friendly automagical mocking for GraphQL
Report Bug · diff --git a/src/__tests__/lib.test.ts b/src/__tests__/lib.test.ts index e9e780e..7e1b084 100644 --- a/src/__tests__/lib.test.ts +++ b/src/__tests__/lib.test.ts @@ -325,7 +325,6 @@ describe("Automocking", () => { }); }); - test.todo("can provide field mock override"); test("automocking of elements are deterministic on some seed", () => { const query = /* GraphQL */ ` fragment ShapeParts on Shape { @@ -349,10 +348,36 @@ describe("Automocking", () => { } `; - const resp: any = ergonomock(schema, query, {}, "this-is-the-randomizer-seed"); - const resp2: any = ergonomock(schema, query, {}, "this-is-the-randomizer-seed"); + const resp: any = ergonomock(schema, query, { mockSeed: "this-is-the-randomizer-seed" }); + const resp2: any = ergonomock(schema, query, { mockSeed: "this-is-the-randomizer-seed" }); expect(resp).toEqual(resp2); }); + + test("Can automock mutations", () => { + const query = /* GraphQL */ ` + mutation SampleQuery($input: ShapeInput!) { + createShape(input: $input) { + id + returnInt + returnString + } + } + `; + + const resp: any = ergonomock(schema, query, { + variables: { + input: { + someID: "123", + someInt: 123 + } + } + }); + expect(resp.data.createShape).toMatchObject({ + id: expect.toBeString(), + returnInt: expect.toBeNumber(), + returnString: expect.toBeString() + }); + }); }); describe("With partial mocks provided", () => { @@ -373,7 +398,7 @@ describe("Automocking", () => { returnFloat: 10.2, returnBoolean: false }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { @@ -399,7 +424,7 @@ describe("Automocking", () => { returnFloatList: [10.2, 10.2], returnBooleanList: [false] }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { @@ -421,7 +446,7 @@ describe("Automocking", () => { const mocks = { returnEnum: "A" }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { @@ -446,7 +471,7 @@ describe("Automocking", () => { const mocks = { returnEnumList: ["A", "C"] }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { @@ -471,7 +496,7 @@ describe("Automocking", () => { const mocks = { returnEnum: "D" }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp.errors[0].message).toBe('Expected a value of type "SomeEnum" but received: "D"'); expect(resp).toMatchObject({ data: { @@ -519,7 +544,7 @@ describe("Automocking", () => { } } }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { returnEnum: expect.toBeOneOf(["A", "B", "C"]), @@ -615,7 +640,7 @@ describe("Automocking", () => { } ] }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { returnEnum: expect.toBeOneOf(["A", "B", "C"]), @@ -711,7 +736,7 @@ describe("Automocking", () => { { __typename: "Bee", returnEnum: "B" } ] }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { returnEnum: expect.toBeOneOf(["A", "B", "C"]), @@ -748,7 +773,7 @@ describe("Automocking", () => { { __typename: "Bee", returnEnum: "B" } ] }; - const resp: any = ergonomock(schema, query, mocks); + const resp: any = ergonomock(schema, query, { mocks }); expect(resp).toMatchObject({ data: { returnEnum: expect.toBeOneOf(["A", "B", "C"]), @@ -787,7 +812,7 @@ describe("Automocking", () => { } } }; - const resp: any = ergonomock(schema, testQuery, mocks); + const resp: any = ergonomock(schema, testQuery, { mocks }); expect(resp.data.returnShape).toMatchObject({ id: expect.toBeString(), returnInt: 4, @@ -825,7 +850,7 @@ describe("Automocking", () => { } } }; - const resp: any = ergonomock(schema, testQuery, mocks); + const resp: any = ergonomock(schema, testQuery, { mocks }); expect(resp.data.returnShape.nestedShape).toMatchObject({ returnIntList: expect.toBeArray(), returnStringList: expect.toBeArray(), @@ -845,6 +870,37 @@ describe("Automocking", () => { expect(getLastElement(nestedShape.returnEnumList)).toBeOneOf(["A", "B", "C"]); expect(getLastElement(nestedShape.returnStringList)).toBeString(); }); + + test("Can partially mock mutations", () => { + const query = /* GraphQL */ ` + mutation SampleQuery($input: ShapeInput!) { + createShape(input: $input) { + id + returnInt + returnString + } + } + `; + + const resp: any = ergonomock(schema, query, { + mocks: { + createShape: { + id: "567" + } + }, + variables: { + input: { + someID: "123", + someInt: 123 + } + } + }); + expect(resp.data.createShape).toMatchObject({ + id: "567", + returnInt: expect.toBeNumber(), + returnString: expect.toBeString() + }); + }); }); describe("calling mock functions", () => { @@ -873,11 +929,44 @@ describe("Automocking", () => { } } }; - const resp: any = ergonomock(schema, testQuery, mocks); + const resp: any = ergonomock(schema, testQuery, { mocks }); expect(resp.data.returnShape.nestedShape.returnInt).toBe(1234); expect(rootReturnIntValue).toBe(4321); expect(resp.data.returnShape.returnInt).toBe(4321); }); + + test("Can partially mock mutations with functions", () => { + const query = /* GraphQL */ ` + mutation SampleQuery($input: ShapeInput!) { + createShape(input: $input) { + id + returnInt + returnString + } + } + `; + + const resp: any = ergonomock(schema, query, { + mocks: { + createShape: (a, args, c, d) => { + return { + id: args.input.someID + }; + } + }, + variables: { + input: { + someID: "123", + someInt: 111 + } + } + }); + expect(resp.data.createShape).toMatchObject({ + id: "123", + returnInt: expect.toBeNumber(), + returnString: expect.toBeString() + }); + }); }); describe("mocking errors", () => { @@ -889,7 +978,9 @@ describe("Automocking", () => { } `; const resp: any = ergonomock(schema, testQuery, { - returnInt: new Error("foo bar") + mocks: { + returnInt: new Error("foo bar") + } }); expect(resp.data).toMatchObject({ returnInt: null, @@ -906,7 +997,9 @@ describe("Automocking", () => { } `; const resp: any = ergonomock(schema, testQuery, { - returnEnum: new Error("foo enum") + mocks: { + returnEnum: new Error("foo enum") + } }); expect(resp.data).toMatchObject({ returnEnum: null, @@ -925,7 +1018,9 @@ describe("Automocking", () => { } `; const resp: any = ergonomock(schema, testQuery, { - returnShape: new Error("foo shape") + mocks: { + returnShape: new Error("foo shape") + } }); expect(resp.data).toMatchObject({ returnShape: null, @@ -942,7 +1037,9 @@ describe("Automocking", () => { } `; const resp: any = ergonomock(schema, testQuery, { - returnStringList: ["Whiskey", new Error("foo Tango"), "Foxtrot"] + mocks: { + returnStringList: ["Whiskey", new Error("foo Tango"), "Foxtrot"] + } }); expect(resp.data).toMatchObject({ returnStringList: ["Whiskey", null, "Foxtrot"], @@ -964,7 +1061,13 @@ describe("Automocking", () => { } `; const resp: any = ergonomock(schema, testQuery, { - returnBirdsAndBees: [{ __typename: "Bird" }, new Error("foo Tango"), { __typename: "Bee" }] + mocks: { + returnBirdsAndBees: [ + { __typename: "Bird" }, + new Error("foo Tango"), + { __typename: "Bee" } + ] + } }); expect(resp.data).toMatchObject({ returnBirdsAndBees: [ @@ -987,7 +1090,9 @@ describe("Automocking", () => { } `; const resp: any = ergonomock(schema, testQuery, { - returnFlying: [{ __typename: "Bird" }, new Error("foo Tango"), { __typename: "Bee" }] + mocks: { + returnFlying: [{ __typename: "Bird" }, new Error("foo Tango"), { __typename: "Bee" }] + } }); expect(resp.data).toMatchObject({ returnFlying: [ @@ -1009,8 +1114,10 @@ describe("Automocking", () => { } `; const resp: any = ergonomock(schema, testQuery, { - returnShape: () => { - throw new Error("foo shape"); + mocks: { + returnShape: () => { + throw new Error("foo shape"); + } } }); expect(resp.data).toMatchObject({ @@ -1020,51 +1127,4 @@ describe("Automocking", () => { expect(resp.errors).toStrictEqual([new GraphQLError("foo shape")]); }); }); - - test("base case - TBD remove this test later", () => { - const query = /* GraphQL */ ` - query SampleQuery { - returnInt - returnString - returnFlying { - __typename - id - ... on Bird { - returnString - } - ... on Bee { - returnEnum - } - returnInt - } - } - `; - - const mocks = { - returnString: "bar", - returnFlying: [ - { __typename: "Bee", id: "123" }, - { __typename: "Bee" }, - { __typename: "Bird" } - ] - }; - - const resp: any = ergonomock(schema, query, mocks); - - expect(resp).toMatchObject({ - data: { - returnInt: expect.toBeNumber(), - returnString: "bar", - returnFlying: [ - { __typename: "Bee", id: "123", returnEnum: expect.toBeOneOf(["A", "B", "C"]) }, - { - __typename: "Bee", - id: expect.toBeString(), - returnEnum: expect.toBeOneOf(["A", "B", "C"]) - }, - { __typename: "Bird", id: expect.toBeString(), returnString: expect.toBeString() } - ] - } - }); - }); }); diff --git a/src/__tests__/schema.ts b/src/__tests__/schema.ts index 73fe554..23a888f 100644 --- a/src/__tests__/schema.ts +++ b/src/__tests__/schema.ts @@ -73,8 +73,13 @@ const schemaSDL = /* GraphQL */ ` node(id: String!): Flying node2(id: String!): BirdsAndBees } + input ShapeInput { + someID: ID! + someInt: Int + } type RootMutation { returnStringArgument(s: String): String + createShape(input: ShapeInput): Shape } schema { query: RootQuery diff --git a/src/apollo/MockLink.ts b/src/apollo/MockLink.ts index edc2510..8e8f04c 100644 --- a/src/apollo/MockLink.ts +++ b/src/apollo/MockLink.ts @@ -49,7 +49,11 @@ export default class MockLink extends ApolloLink { }); // Call ergonomock() to get results - const result = ergonomock(this.schema, operation.query, mock || {}, seed); + const result = ergonomock(this.schema, operation.query, { + mocks: mock || {}, + mockSeed: seed, + variables: operation.variables + }); // Return Observer to be compatible with apollo return new Observable(observer => { diff --git a/src/index.ts b/src/index.ts index 244699c..a282b7a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,6 @@ -export { ergonomock } from "./mock"; -export { default as ErgonoMockedProvider } from "./apollo/ErgonoMockedProvider"; -export { default as MockLink } from "./apollo/MockLink"; +export { ergonomock, ErgonoMockShape } from "./mock"; +export { + default as ErgonoMockedProvider, + ErgonoMockedProviderProps +} from "./apollo/ErgonoMockedProvider"; +export { default as MockLink, ApolloErgonoMockMap } from "./apollo/MockLink"; diff --git a/src/mock.ts b/src/mock.ts index 404e936..75cce32 100644 --- a/src/mock.ts +++ b/src/mock.ts @@ -35,12 +35,19 @@ export type ErgonoMockShape = { [k: string]: ErgonoMockShape | ErgonoMockLeaf | Array; }; +export type ErgonomockOptions = { + mocks?: ErgonoMockShape; + mockSeed?: string; + variables?: Record; +}; + export function ergonomock( schema: GraphQLSchema, query: string | DocumentNode, - mocks?: ErgonoMockShape, - mockSeed?: string + options: ErgonomockOptions = {} ) { + const { mocks, mockSeed, variables = {} } = options; + // Guard rails for schema & query if (!schema || !isSchema(schema)) { throw new Error("Ergonomock requires a valid GraphQL schema."); @@ -61,21 +68,17 @@ export function ergonomock( const mockResolverFunction = function( type: GraphQLType, - typeName?: string, // TODO: get rid of this? fieldName?: string ): GraphQLFieldResolver { - // TODO: clean up this comment, which was taken as-is from apollo // order of precendence for mocking: - // 1. if the object passed in already has fieldName, just use that + // 1. if the object passed in already has fieldName, just use that value // --> if it's a function, that becomes your resolver // --> if it's a value, the mock resolver will return that // 2. if the nullableType is a list, recurse - // 2. if there's a mock defined for this typeName, that will be used // 3. if there's no mock defined, use the default mocks for this type return (root, args, context, info) => { // nullability doesn't matter for the purpose of mocking. const fieldType = getNullableType(type) as GraphQLNullableType; - const namedFieldType = getNamedType(fieldType); // TODO: get rid of this? if (root && fieldName && typeof root[fieldName] !== "undefined") { const mock = root[fieldName]; @@ -135,15 +138,10 @@ export function ergonomock( if (isOnQueryType || isOnMutationType) { mockResolver = (root, args, context, info) => { - return mockResolverFunction(field.type, typeName, fieldName)( - mocks || {}, - args, - context, - info - ); + return mockResolverFunction(field.type, fieldName)(mocks || {}, args, context, info); }; } else { - mockResolver = mockResolverFunction(field.type, typeName, fieldName); + mockResolver = mockResolverFunction(field.type, fieldName); } field.resolve = mockResolver; }); @@ -152,7 +150,8 @@ export function ergonomock( schema, document, rootValue: {}, - contextValue: {} + contextValue: {}, + variableValues: variables }); return resp; }